summaryrefslogtreecommitdiff
diff options
authorPaulo Henrique de Lima Santana (phls) <phls@debian.org>2020-12-23 23:27:19 -0300
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2020-12-24 10:37:47 +0000
commitba80e4edf62f807e4aeb8c676972b15071f80b05 (patch)
tree1a1e644afb805cbefd221cf24a0a3a2ce383b305
parent1f767cbb1f3e9fdd34caade8384aca78438ebd55 (diff)
Imported using git-ubuntu import.
Notes
Notes: * New upstream version 4.0.0. * debian/control: - Added Multi-Arch: foreign; - Bumped Standards-Version to 4.5.1. * debian/copyright: - Updated packaging copyright years. - Updated upstream copyright years. * debian/manpage: updated manpage with a file from upstream. * debian/tests/*: updated files to improve tests.
-rw-r--r--PKG-INFO11
-rw-r--r--README.rst7
-rw-r--r--debian/changelog14
-rw-r--r--debian/control4
-rw-r--r--debian/copyright4
-rw-r--r--debian/manpage/python-engineio.1225
-rw-r--r--debian/tests/asyncio/test_async_aiohttp.py38
-rw-r--r--debian/tests/asyncio/test_async_asgi.py430
-rw-r--r--debian/tests/asyncio/test_async_sanic.py1
-rw-r--r--debian/tests/asyncio/test_async_tornado.py28
-rw-r--r--debian/tests/asyncio/test_asyncio_client.py1133
-rw-r--r--debian/tests/asyncio/test_asyncio_server.py817
-rw-r--r--debian/tests/asyncio/test_asyncio_socket.py357
-rw-r--r--debian/tests/common/test_async_eventlet.py19
-rw-r--r--debian/tests/common/test_client.py1310
-rw-r--r--debian/tests/common/test_middleware.py125
-rw-r--r--debian/tests/common/test_packet.py112
-rw-r--r--debian/tests/common/test_payload.py118
-rw-r--r--debian/tests/common/test_server.py826
-rw-r--r--debian/tests/common/test_socket.py359
-rw-r--r--engineio/__init__.py2
-rw-r--r--engineio/async_drivers/aiohttp.py5
-rw-r--r--engineio/async_drivers/asgi.py50
-rw-r--r--engineio/async_drivers/gevent_uwsgi.py8
-rw-r--r--engineio/async_drivers/sanic.py17
-rw-r--r--engineio/async_drivers/tornado.py8
-rw-r--r--engineio/asyncio_client.py176
-rw-r--r--engineio/asyncio_server.py167
-rw-r--r--engineio/asyncio_socket.py75
-rw-r--r--engineio/client.py210
-rw-r--r--engineio/packet.py78
-rw-r--r--engineio/payload.py71
-rw-r--r--engineio/server.py221
-rw-r--r--engineio/socket.py66
-rw-r--r--python_engineio.egg-info/PKG-INFO11
-rw-r--r--python_engineio.egg-info/requires.txt1
-rwxr-xr-xsetup.py4
37 files changed, 4182 insertions, 2926 deletions
diff --git a/PKG-INFO b/PKG-INFO
index d913b64..4aca5ae 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: python-engineio
-Version: 3.11.1
+Version: 4.0.0
Summary: Engine.IO server
Home-page: http://github.com/miguelgrinberg/python-engineio/
Author: Miguel Grinberg
@@ -9,8 +9,11 @@ License: MIT
Description: python-engineio
===============
- .. image:: https://travis-ci.org/miguelgrinberg/python-engineio.svg?branch=master
- :target: https://travis-ci.org/miguelgrinberg/python-engineio
+ .. image:: https://github.com/miguelgrinberg/python-engineio/workflows/build/badge.svg
+ :target: https://github.com/miguelgrinberg/python-engineio/actions
+
+ .. image:: https://codecov.io/gh/miguelgrinberg/python-engineio/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/miguelgrinberg/python-engineio
Python implementation of the `Engine.IO`_ realtime client and server.
@@ -39,5 +42,5 @@ Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Provides-Extra: asyncio_client
Provides-Extra: client
+Provides-Extra: asyncio_client
diff --git a/README.rst b/README.rst
index a36d069..7b29b9f 100644
--- a/README.rst
+++ b/README.rst
@@ -1,8 +1,11 @@
python-engineio
===============
-.. image:: https://travis-ci.org/miguelgrinberg/python-engineio.svg?branch=master
- :target: https://travis-ci.org/miguelgrinberg/python-engineio
+.. image:: https://github.com/miguelgrinberg/python-engineio/workflows/build/badge.svg
+ :target: https://github.com/miguelgrinberg/python-engineio/actions
+
+.. image:: https://codecov.io/gh/miguelgrinberg/python-engineio/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/miguelgrinberg/python-engineio
Python implementation of the `Engine.IO`_ realtime client and server.
diff --git a/debian/changelog b/debian/changelog
index 0b217d3..d5701e3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+python-engineio (4.0.0-1) unstable; urgency=medium
+
+ * New upstream version 4.0.0.
+ * debian/control:
+ - Added Multi-Arch: foreign;
+ - Bumped Standards-Version to 4.5.1.
+ * debian/copyright:
+ - Updated packaging copyright years.
+ - Updated upstream copyright years.
+ * debian/manpage: updated manpage with a file from upstream.
+ * debian/tests/*: updated files to improve tests.
+
+ -- Paulo Henrique de Lima Santana (phls) <phls@debian.org> Wed, 23 Dec 2020 23:27:19 -0300
+
python-engineio (3.11.1-1) unstable; urgency=medium
* New upstream version 3.11.1. Consequently fix CVE-2019-13611: websocket
diff --git a/debian/control b/debian/control
index 995d97a..b3c12d1 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: python-engineio
Section: python
Priority: optional
Maintainer: Paulo Henrique de Lima Santana (phls) <phls@debian.org>
-Build-Depends: debhelper-compat (= 12),
+Build-Depends: debhelper-compat (= 13),
dh-python,
python3,
python3-aiohttp,
@@ -17,7 +17,7 @@ Build-Depends: debhelper-compat (= 12),
python3-tornado,
python3-websocket,
X-Python3-Version: >= 3.7
-Standards-Version: 4.4.1
+Standards-Version: 4.5.1
Rules-Requires-Root: no
Homepage: https://pypi.python.org/pypi/python-engineio
Vcs-Browser: https://salsa.debian.org/debian/python-engineio
diff --git a/debian/copyright b/debian/copyright
index 95945a6..98c3abf 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -35,13 +35,13 @@ Files: engineio/async_drivers/__init__.py
README.rst
setup.cfg
setup.py
-Copyright: 2015-2019, Miguel Grinberg <miguelgrinberg50@gmail.com>
+Copyright: 2015-2020, Miguel Grinberg <miguelgrinberg50@gmail.com>
License: MIT
Comment:
Using license as found in LICENSE file.
Files: debian/*
-Copyright: 2017-2019, Paulo Henrique de Lima Santana (phls) <phls@debian.org>
+Copyright: 2017-2020, Paulo Henrique de Lima Santana (phls) <phls@debian.org>
License: MIT
License: MIT
diff --git a/debian/manpage/python-engineio.1 b/debian/manpage/python-engineio.1
index 5136735..06b8ce8 100644
--- a/debian/manpage/python-engineio.1
+++ b/debian/manpage/python-engineio.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH "PYTHON-ENGINEIO" "1" "Dec 26, 2019" "" "python-engineio"
+.TH "PYTHON-ENGINEIO" "1" "Dec 24, 2020" "" "python-engineio"
.SH NAME
python-engineio \- python-engineio Documentation
.
@@ -105,7 +105,7 @@ client:
.IP \(bu 2
Can connect to other Engine.IO complaint servers besides the one in this package.
.IP \(bu 2
-Compatible with Python 2.7 and 3.5+.
+Compatible with Python 3.5+.
.IP \(bu 2
Two versions of the client, one for standard Python and another for \fBasyncio\fP\&.
.IP \(bu 2
@@ -191,7 +191,7 @@ if __name__ == \(aq__main__\(aq:
Can accept clients running other complaint Engine.IO clients besides the one in this
package.
.IP \(bu 2
-Compatible with Python 2.7 and Python 3.5+.
+Compatible with Python 3.5+.
.IP \(bu 2
Two versions of the server, one for standard Python and another for \fBasyncio\fP\&.
.IP \(bu 2
@@ -530,6 +530,33 @@ await eio.sleep(2)
.sp
The single argument passed to the method is the number of seconds to sleep
for.
+.SS Debugging and Troubleshooting
+.sp
+To help you debug issues, the client can be configured to output logs to the
+terminal:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+import engineio
+
+# standard Python
+eio = engineio.Client(logger=True)
+
+# asyncio
+eio = engineio.AsyncClient(logger=True)
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+The \fBlogger\fP argument can be set to \fBTrue\fP to output logs to \fBstderr\fP, or
+to an object compatible with Python\(aqs \fBlogging\fP package where the logs should
+be emitted to. A value of \fBFalse\fP disables logging.
+.sp
+Logging can help identify the cause of connection problems, unexpected
+disconnections and other issues.
.SH THE ENGINE.IO SERVER
.sp
This package contains two Engine.IO servers:
@@ -807,6 +834,14 @@ The \fBsid\fP argument passed into all the event handlers is a connection
identifier for the client. All the events from a client will use the same
\fBsid\fP value.
.sp
+The \fBconnect\fP handler is the place where the server can perform
+authentication. The value returned by this handler is used to determine if the
+connection is accepted or rejected. When the handler does not return any value
+(which is the same as returning \fBNone\fP) or when it returns \fBTrue\fP the
+connection is accepted. If the handler returns \fBFalse\fP or any JSON
+compatible data type (string, integer, list or dictionary) the connection is
+rejected. A rejected connection triggers a response with a 401 status code.
+.sp
The \fBdata\fP argument passed to the \fB\(aqmessage\(aq\fP event handler contains
application\-specific data provided by the client with the event.
.SS Sending Messages
@@ -1030,6 +1065,33 @@ await eio.sleep(2)
.sp
The single argument passed to the method is the number of seconds to sleep
for.
+.SS Debugging and Troubleshooting
+.sp
+To help you debug issues, the server can be configured to output logs to the
+terminal:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+import engineio
+
+# standard Python
+eio = engineio.Server(logger=True)
+
+# asyncio
+eio = engineio.AsyncServer(logger=True)
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+The \fBlogger\fP argument can be set to \fBTrue\fP to output logs to \fBstderr\fP, or
+to an object compatible with Python\(aqs \fBlogging\fP package where the logs should
+be emitted to. A value of \fBFalse\fP disables logging.
+.sp
+Logging can help identify the cause of connection problems, 400 responses,
+bad performance and other issues.
.SS Deployment Strategies
.sp
The following sections describe a variety of deployment strategies for
@@ -1518,7 +1580,7 @@ attacks.
.SS \fBClient\fP class
.INDENT 0.0
.TP
-.B class engineio.Client(logger=False, json=None, request_timeout=5, ssl_verify=True)
+.B class engineio.Client(logger=False, json=None, request_timeout=5, http_session=None, ssl_verify=True)
An Engine.IO client.
.sp
This class implements a fully compliant Engine.IO web client with support
@@ -1530,7 +1592,8 @@ for websocket and long\-polling transports.
.IP \(bu 2
\fBlogger\fP \-\- To enable logging set to \fBTrue\fP or pass a logger object to
use. To disable logging set to \fBFalse\fP\&. The default is
-\fBFalse\fP\&.
+\fBFalse\fP\&. Note that fatal errors are logged even when
+\fBlogger\fP is \fBFalse\fP\&.
.IP \(bu 2
\fBjson\fP \-\- An alternative json module to use for encoding and decoding
packets. Custom json modules must have \fBdumps\fP and \fBloads\fP
@@ -1540,6 +1603,11 @@ versions.
\fBrequest_timeout\fP \-\- A timeout in seconds for requests. The default is
5 seconds.
.IP \(bu 2
+\fBhttp_session\fP \-\- an initialized \fBrequests.Session\fP object to be used
+when sending requests to the server. Use it if you
+need to add special client options such as proxy
+servers, SSL certificates, etc.
+.IP \(bu 2
\fBssl_verify\fP \-\- \fBTrue\fP to verify SSL certificates, or \fBFalse\fP to
skip SSL certificate verification, allowing
connections to servers with self signed certificates.
@@ -1548,7 +1616,7 @@ The default is \fBTrue\fP\&.
.UNINDENT
.INDENT 7.0
.TP
-.B connect(url, headers={}, transports=None, engineio_path=\(aqengine.io\(aq)
+.B connect(url, headers=None, transports=None, engineio_path=\(aqengine.io\(aq)
Connect to an Engine.IO server.
.INDENT 7.0
.TP
@@ -1647,22 +1715,14 @@ eio.on(\(aqmessage\(aq, message_handler)
.UNINDENT
.INDENT 7.0
.TP
-.B send(data, binary=None)
+.B send(data)
Send a message to a client.
.INDENT 7.0
.TP
.B Parameters
-.INDENT 7.0
-.IP \(bu 2
\fBdata\fP \-\- The data to send to the client. Data can be of type
\fBstr\fP, \fBbytes\fP, \fBlist\fP or \fBdict\fP\&. If a \fBlist\fP
or \fBdict\fP, the data will be serialized as JSON.
-.IP \(bu 2
-\fBbinary\fP \-\- \fBTrue\fP to send packet as binary, \fBFalse\fP to send
-as text. If not given, unicode (Python 2) and str
-(Python 3) are sent as text, and str (Python 2) and
-bytes (Python 3) are sent as binary.
-.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 7.0
@@ -1714,7 +1774,7 @@ during the life of the connection.
.SS \fBAsyncClient\fP class
.INDENT 0.0
.TP
-.B class engineio.AsyncClient(logger=False, json=None, request_timeout=5, ssl_verify=True)
+.B class engineio.AsyncClient(logger=False, json=None, request_timeout=5, http_session=None, ssl_verify=True)
An Engine.IO client for asyncio.
.sp
This class implements a fully compliant Engine.IO web client with support
@@ -1727,7 +1787,8 @@ framework on Python 3.5 or newer.
.IP \(bu 2
\fBlogger\fP \-\- To enable logging set to \fBTrue\fP or pass a logger object to
use. To disable logging set to \fBFalse\fP\&. The default is
-\fBFalse\fP\&.
+\fBFalse\fP\&. Note that fatal errors are logged even when
+\fBlogger\fP is \fBFalse\fP\&.
.IP \(bu 2
\fBjson\fP \-\- An alternative json module to use for encoding and decoding
packets. Custom json modules must have \fBdumps\fP and \fBloads\fP
@@ -1737,6 +1798,11 @@ versions.
\fBrequest_timeout\fP \-\- A timeout in seconds for requests. The default is
5 seconds.
.IP \(bu 2
+\fBhttp_session\fP \-\- an initialized \fBaiohttp.ClientSession\fP object to be
+used when sending requests to the server. Use it if
+you need to add special client options such as proxy
+servers, SSL certificates, etc.
+.IP \(bu 2
\fBssl_verify\fP \-\- \fBTrue\fP to verify SSL certificates, or \fBFalse\fP to
skip SSL certificate verification, allowing
connections to servers with self signed certificates.
@@ -1745,7 +1811,7 @@ The default is \fBTrue\fP\&.
.UNINDENT
.INDENT 7.0
.TP
-.B connect(url, headers={}, transports=None, engineio_path=\(aqengine.io\(aq)
+.B async connect(url, headers=None, transports=None, engineio_path=\(aqengine.io\(aq)
Connect to an Engine.IO server.
.INDENT 7.0
.TP
@@ -1796,7 +1862,7 @@ Create a queue object.
.UNINDENT
.INDENT 7.0
.TP
-.B disconnect(abort=False)
+.B async disconnect(abort=False)
Disconnect from the server.
.INDENT 7.0
.TP
@@ -1848,29 +1914,21 @@ eio.on(\(aqmessage\(aq, message_handler)
.UNINDENT
.INDENT 7.0
.TP
-.B send(data, binary=None)
+.B async send(data)
Send a message to a client.
.INDENT 7.0
.TP
.B Parameters
-.INDENT 7.0
-.IP \(bu 2
\fBdata\fP \-\- The data to send to the client. Data can be of type
\fBstr\fP, \fBbytes\fP, \fBlist\fP or \fBdict\fP\&. If a \fBlist\fP
or \fBdict\fP, the data will be serialized as JSON.
-.IP \(bu 2
-\fBbinary\fP \-\- \fBTrue\fP to send packet as binary, \fBFalse\fP to send
-as text. If not given, unicode (Python 2) and str
-(Python 3) are sent as text, and str (Python 2) and
-bytes (Python 3) are sent as binary.
-.UNINDENT
.UNINDENT
.sp
Note: this method is a coroutine.
.UNINDENT
.INDENT 7.0
.TP
-.B sleep(seconds=0)
+.B async sleep(seconds=0)
Sleep for the requested amount of time.
.sp
Note: this method is a coroutine.
@@ -1911,7 +1969,7 @@ The possible values returned by this function are \fB\(aqpolling\(aq\fP and
.UNINDENT
.INDENT 7.0
.TP
-.B wait()
+.B async wait()
Wait until the connection with the server ends.
.sp
Client applications can use this function to block the main thread
@@ -1923,7 +1981,7 @@ Note: this method is a coroutine.
.SS \fBServer\fP class
.INDENT 0.0
.TP
-.B class engineio.Server(async_mode=None, ping_timeout=60, ping_interval=25, max_http_buffer_size=100000000, allow_upgrades=True, http_compression=True, compression_threshold=1024, cookie=\(aqio\(aq, cors_allowed_origins=None, cors_credentials=True, logger=False, json=None, async_handlers=True, monitor_clients=None, **kwargs)
+.B class engineio.Server(async_mode=None, ping_interval=25, ping_timeout=5, max_http_buffer_size=1000000, allow_upgrades=True, http_compression=True, compression_threshold=1024, cookie=None, cors_allowed_origins=None, cors_credentials=True, logger=False, json=None, async_handlers=True, monitor_clients=None, **kwargs)
An Engine.IO server.
.sp
This class implements a fully compliant Engine.IO web server with support
@@ -1942,19 +2000,18 @@ argument is not given, "eventlet" is tried first, then
The first async mode that has all its dependencies
installed is the one that is chosen.
.IP \(bu 2
-\fBping_timeout\fP \-\- The time in seconds that the client waits for the
-server to respond before disconnecting. The default
-is 60 seconds.
-.IP \(bu 2
-\fBping_interval\fP \-\- The interval in seconds at which the client pings
-the server. The default is 25 seconds. For advanced
+\fBping_interval\fP \-\- The interval in seconds at which the server pings
+the client. The default is 25 seconds. For advanced
control, a two element tuple can be given, where
the first number is the ping interval and the second
-is a grace period added by the server. The default
-grace period is 5 seconds.
+is a grace period added by the server.
+.IP \(bu 2
+\fBping_timeout\fP \-\- The time in seconds that the client waits for the
+server to respond before disconnecting. The default
+is 5 seconds.
.IP \(bu 2
\fBmax_http_buffer_size\fP \-\- The maximum size of a message when using the
-polling transport. The default is 100,000,000
+polling transport. The default is 1,000,000
bytes.
.IP \(bu 2
\fBallow_upgrades\fP \-\- Whether to allow transport upgrades or not. The
@@ -1967,9 +2024,14 @@ polling transport. The default is \fBTrue\fP\&.
is greater than this value. The default is
1024 bytes.
.IP \(bu 2
-\fBcookie\fP \-\- Name of the HTTP cookie that contains the client session
-id. If set to \fBNone\fP, a cookie is not sent to the client.
-The default is \fB\(aqio\(aq\fP\&.
+\fBcookie\fP \-\- If set to a string, it is the name of the HTTP cookie the
+server sends back tot he client containing the client
+session id. If set to a dictionary, the \fB\(aqname\(aq\fP key
+contains the cookie name and other keys define cookie
+attributes, where the value of each attribute can be a
+string, a callable with no arguments, or a boolean. If set
+to \fBNone\fP (the default), a cookie is not sent to the
+client.
.IP \(bu 2
\fBcors_allowed_origins\fP \-\- Origin or list of origins that are allowed to
connect to this server. Only the same origin
@@ -1983,7 +2045,8 @@ is \fBTrue\fP\&.
.IP \(bu 2
\fBlogger\fP \-\- To enable logging set to \fBTrue\fP or pass a logger object to
use. To disable logging set to \fBFalse\fP\&. The default is
-\fBFalse\fP\&.
+\fBFalse\fP\&. Note that fatal errors are logged even when
+\fBlogger\fP is \fBFalse\fP\&.
.IP \(bu 2
\fBjson\fP \-\- An alternative json module to use for encoding and decoding
packets. Custom json modules must have \fBdumps\fP and \fBloads\fP
@@ -2034,6 +2097,11 @@ is not given, then all clients are closed.
.UNINDENT
.INDENT 7.0
.TP
+.B generate_id()
+Generate a unique session id.
+.UNINDENT
+.INDENT 7.0
+.TP
.B get_queue_empty_exception()
Return the queue empty exception for the appropriate async model.
.sp
@@ -2144,7 +2212,7 @@ Store the user session for a client.
.UNINDENT
.INDENT 7.0
.TP
-.B send(sid, data, binary=None)
+.B send(sid, data)
Send a message to a client.
.INDENT 7.0
.TP
@@ -2156,11 +2224,6 @@ Send a message to a client.
\fBdata\fP \-\- The data to send to the client. Data can be of type
\fBstr\fP, \fBbytes\fP, \fBlist\fP or \fBdict\fP\&. If a \fBlist\fP
or \fBdict\fP, the data will be serialized as JSON.
-.IP \(bu 2
-\fBbinary\fP \-\- \fBTrue\fP to send packet as binary, \fBFalse\fP to send
-as text. If not given, unicode (Python 2) and str
-(Python 3) are sent as text, and str (Python 2) and
-bytes (Python 3) are sent as binary.
.UNINDENT
.UNINDENT
.UNINDENT
@@ -2251,7 +2314,7 @@ and \fB\(aqwebsocket\(aq\fP\&.
.SS \fBAsyncServer\fP class
.INDENT 0.0
.TP
-.B class engineio.AsyncServer(async_mode=None, ping_timeout=60, ping_interval=25, max_http_buffer_size=100000000, allow_upgrades=True, http_compression=True, compression_threshold=1024, cookie=\(aqio\(aq, cors_allowed_origins=None, cors_credentials=True, logger=False, json=None, async_handlers=True, monitor_clients=None, **kwargs)
+.B class engineio.AsyncServer(async_mode=None, ping_interval=25, ping_timeout=5, max_http_buffer_size=1000000, allow_upgrades=True, http_compression=True, compression_threshold=1024, cookie=None, cors_allowed_origins=None, cors_credentials=True, logger=False, json=None, async_handlers=True, monitor_clients=None, **kwargs)
An Engine.IO server for asyncio.
.sp
This class implements a fully compliant Engine.IO web server with support
@@ -2271,18 +2334,19 @@ given, "aiohttp" is tried first, followed by "sanic",
has all its dependencies installed is the one that is
chosen.
.IP \(bu 2
-\fBping_timeout\fP \-\- The time in seconds that the client waits for the
-server to respond before disconnecting.
-.IP \(bu 2
-\fBping_interval\fP \-\- The interval in seconds at which the client pings
-the server. The default is 25 seconds. For advanced
+\fBping_interval\fP \-\- The interval in seconds at which the server pings
+the client. The default is 25 seconds. For advanced
control, a two element tuple can be given, where
the first number is the ping interval and the second
-is a grace period added by the server. The default
-grace period is 5 seconds.
+is a grace period added by the server.
+.IP \(bu 2
+\fBping_timeout\fP \-\- The time in seconds that the client waits for the
+server to respond before disconnecting. The default
+is 5 seconds.
.IP \(bu 2
\fBmax_http_buffer_size\fP \-\- The maximum size of a message when using the
-polling transport.
+polling transport. The default is 1,000,000
+bytes.
.IP \(bu 2
\fBallow_upgrades\fP \-\- Whether to allow transport upgrades or not.
.IP \(bu 2
@@ -2292,8 +2356,14 @@ polling transport.
\fBcompression_threshold\fP \-\- Only compress messages when their byte size
is greater than this value.
.IP \(bu 2
-\fBcookie\fP \-\- Name of the HTTP cookie that contains the client session
-id. If set to \fBNone\fP, a cookie is not sent to the client.
+\fBcookie\fP \-\- If set to a string, it is the name of the HTTP cookie the
+server sends back tot he client containing the client
+session id. If set to a dictionary, the \fB\(aqname\(aq\fP key
+contains the cookie name and other keys define cookie
+attributes, where the value of each attribute can be a
+string, a callable with no arguments, or a boolean. If set
+to \fBNone\fP (the default), a cookie is not sent to the
+client.
.IP \(bu 2
\fBcors_allowed_origins\fP \-\- Origin or list of origins that are allowed to
connect to this server. Only the same origin
@@ -2305,7 +2375,8 @@ disable CORS handling.
allowed in requests to this server.
.IP \(bu 2
\fBlogger\fP \-\- To enable logging set to \fBTrue\fP or pass a logger object to
-use. To disable logging set to \fBFalse\fP\&.
+use. To disable logging set to \fBFalse\fP\&. Note that fatal
+errors are logged even when \fBlogger\fP is \fBFalse\fP\&.
.IP \(bu 2
\fBjson\fP \-\- An alternative json module to use for encoding and decoding
packets. Custom json modules must have \fBdumps\fP and \fBloads\fP
@@ -2347,7 +2418,7 @@ async mode. For asyncio based async modes, this returns an instance of
.UNINDENT
.INDENT 7.0
.TP
-.B disconnect(sid=None)
+.B async disconnect(sid=None)
Disconnect a client.
.INDENT 7.0
.TP
@@ -2360,6 +2431,11 @@ Note: this method is a coroutine.
.UNINDENT
.INDENT 7.0
.TP
+.B generate_id()
+Generate a unique session id.
+.UNINDENT
+.INDENT 7.0
+.TP
.B get_queue_empty_exception()
Return the queue empty exception for the appropriate async model.
.sp
@@ -2370,7 +2446,7 @@ instance of \fBasyncio.QueueEmpty\fP\&.
.UNINDENT
.INDENT 7.0
.TP
-.B get_session(sid)
+.B async get_session(sid)
Return the user session for a client.
.INDENT 7.0
.TP
@@ -2384,7 +2460,7 @@ the user session, use the \fBsession\fP context manager instead.
.UNINDENT
.INDENT 7.0
.TP
-.B handle_request(*args, **kwargs)
+.B async handle_request(*args, **kwargs)
Handle an HTTP request from the client.
.sp
This is the entry point of the Engine.IO application. This function
@@ -2442,7 +2518,7 @@ take a second argument.
.UNINDENT
.INDENT 7.0
.TP
-.B save_session(sid, session)
+.B async save_session(sid, session)
Store the user session for a client.
.INDENT 7.0
.TP
@@ -2457,7 +2533,7 @@ Store the user session for a client.
.UNINDENT
.INDENT 7.0
.TP
-.B send(sid, data, binary=None)
+.B async send(sid, data)
Send a message to a client.
.INDENT 7.0
.TP
@@ -2469,11 +2545,6 @@ Send a message to a client.
\fBdata\fP \-\- The data to send to the client. Data can be of type
\fBstr\fP, \fBbytes\fP, \fBlist\fP or \fBdict\fP\&. If a \fBlist\fP
or \fBdict\fP, the data will be serialized as JSON.
-.IP \(bu 2
-\fBbinary\fP \-\- \fBTrue\fP to send packet as binary, \fBFalse\fP to send
-as text. If not given, unicode (Python 2) and str
-(Python 3) are sent as text, and str (Python 2) and
-bytes (Python 3) are sent as binary.
.UNINDENT
.UNINDENT
.sp
@@ -2516,7 +2587,7 @@ def on_message(sid, msg):
.UNINDENT
.INDENT 7.0
.TP
-.B sleep(seconds=0)
+.B async sleep(seconds=0)
Sleep for the requested amount of time using the appropriate async
model.
.sp
@@ -2615,7 +2686,7 @@ eventlet.wsgi.server(eventlet.listen((\(aq\(aq, 8000)), app)
.SS \fBASGIApp\fP class
.INDENT 0.0
.TP
-.B class engineio.ASGIApp(engineio_server, other_asgi_app=None, static_files=None, engineio_path=\(aqengine.io\(aq)
+.B class engineio.ASGIApp(engineio_server, other_asgi_app=None, static_files=None, engineio_path=\(aqengine.io\(aq, on_startup=None, on_shutdown=None)
ASGI application middleware for Engine.IO.
.sp
This middleware dispatches traffic to an Engine.IO application. It can
@@ -2637,6 +2708,12 @@ documentation for details on this argument.
\fBengineio_path\fP \-\- The endpoint where the Engine.IO application should
be installed. The default value is appropriate for
most cases.
+.IP \(bu 2
+\fBon_startup\fP \-\- function to be called on application startup; can be
+coroutine
+.IP \(bu 2
+\fBon_shutdown\fP \-\- function to be called on application shutdown; can be
+coroutine
.UNINDENT
.UNINDENT
.sp
diff --git a/debian/tests/asyncio/test_async_aiohttp.py b/debian/tests/asyncio/test_async_aiohttp.py
index ab943aa..f89fb4b 100644
--- a/debian/tests/asyncio/test_async_aiohttp.py
+++ b/debian/tests/asyncio/test_async_aiohttp.py
@@ -1,17 +1,9 @@
-import sys
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+from engineio.async_drivers import aiohttp as async_aiohttp
-if sys.version_info >= (3, 5):
- from engineio.async_drivers import aiohttp as async_aiohttp
-
-@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+')
class AiohttpTests(unittest.TestCase):
def test_create_route(self):
app = mock.MagicMock()
@@ -25,9 +17,13 @@ class AiohttpTests(unittest.TestCase):
request._message.method = 'PUT'
request._message.path = '/foo/bar?baz=1'
request._message.version = (1, 1)
- request._message.headers = {'a': 'b', 'c-c': 'd', 'c_c': 'e',
- 'content-type': 'application/json',
- 'content-length': 123}
+ request._message.headers = {
+ 'a': 'b',
+ 'c-c': 'd',
+ 'c_c': 'e',
+ 'content-type': 'application/json',
+ 'content-length': 123,
+ }
request._payload = b'hello world'
environ = async_aiohttp.translate_request(request)
expected_environ = {
@@ -44,14 +40,14 @@ class AiohttpTests(unittest.TestCase):
'aiohttp.request': request,
}
for k, v in expected_environ.items():
- self.assertEqual(v, environ[k])
- self.assertTrue(
- environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d')
+ assert v == environ[k]
+ assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d'
# @mock.patch('async_aiohttp.aiohttp.web.Response')
def test_make_response(self):
- rv = async_aiohttp.make_response('202 ACCEPTED', {'foo': 'bar'},
- b'payload', {})
- self.assertEqual(rv.status, 202)
- self.assertEqual(rv.headers['foo'], 'bar')
- self.assertEqual(rv.body, b'payload')
+ rv = async_aiohttp.make_response(
+ '202 ACCEPTED', {'foo': 'bar'}, b'payload', {}
+ )
+ assert rv.status == 202
+ assert rv.headers['foo'] == 'bar'
+ assert rv.body == b'payload'
diff --git a/debian/tests/asyncio/test_async_asgi.py b/debian/tests/asyncio/test_async_asgi.py
index 33e6f10..9559bdb 100644
--- a/debian/tests/asyncio/test_async_asgi.py
+++ b/debian/tests/asyncio/test_async_asgi.py
@@ -1,16 +1,10 @@
+import asyncio
import os
import sys
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
-
-if sys.version_info >= (3, 5):
- import asyncio
- from engineio.async_drivers import asgi as async_asgi
+from engineio.async_drivers import asgi as async_asgi
def AsyncMock(*args, **kwargs):
@@ -33,12 +27,15 @@ def _run(coro):
class AsgiTests(unittest.TestCase):
def test_create_app(self):
app = async_asgi.ASGIApp(
- 'eio', 'other_app', static_files='static_files',
- engineio_path='/foo')
- self.assertEqual(app.engineio_server, 'eio')
- self.assertEqual(app.other_asgi_app, 'other_app')
- self.assertEqual(app.static_files, 'static_files')
- self.assertEqual(app.engineio_path, 'foo'),
+ 'eio',
+ 'other_app',
+ static_files='static_files',
+ engineio_path='/foo',
+ )
+ assert app.engineio_server == 'eio'
+ assert app.other_asgi_app == 'other_app'
+ assert app.static_files == 'static_files'
+ assert app.engineio_path == 'foo'
def test_engineio_routing(self):
mock_server = mock.MagicMock()
@@ -47,7 +44,8 @@ class AsgiTests(unittest.TestCase):
scope = {'type': 'http', 'path': '/engine.io/'}
_run(app(scope, 'receive', 'send'))
mock_server.handle_request.mock.assert_called_once_with(
- scope, 'receive', 'send')
+ scope, 'receive', 'send'
+ )
def test_other_app_routing(self):
other_app = AsyncMock()
@@ -58,48 +56,58 @@ class AsgiTests(unittest.TestCase):
def test_static_file_routing(self):
root_dir = os.path.dirname(__file__)
- app = async_asgi.ASGIApp('eio', static_files={
- '/': root_dir + '/index.html',
- '/foo': {'content_type': 'text/plain',
- 'filename': root_dir + '/index.html'},
- '/static': root_dir,
- '/static/test/': root_dir + '/',
- })
+ app = async_asgi.ASGIApp(
+ 'eio',
+ static_files={
+ '/': root_dir + '/index.html',
+ '/foo': {
+ 'content_type': 'text/plain',
+ 'filename': root_dir + '/index.html',
+ },
+ '/static': root_dir,
+ '/static/test/': root_dir + '/',
+ },
+ )
def check_path(path, status_code, content_type, body):
scope = {'type': 'http', 'path': path}
receive = AsyncMock(return_value={'type': 'http.request'})
send = AsyncMock()
_run(app(scope, receive, send))
- send.mock.assert_any_call({
- 'type': 'http.response.start',
- 'status': status_code,
- 'headers': [(b'Content-Type', content_type.encode('utf-8'))]})
- send.mock.assert_any_call({
- 'type': 'http.response.body',
- 'body': body.encode('utf-8')})
+ send.mock.assert_any_call(
+ {
+ 'type': 'http.response.start',
+ 'status': status_code,
+ 'headers': [
+ (b'Content-Type', content_type.encode('utf-8'))
+ ],
+ }
+ )
+ send.mock.assert_any_call(
+ {'type': 'http.response.body', 'body': body.encode('utf-8')}
+ )
check_path('/', 200, 'text/html', '<html></html>\n')
check_path('/foo', 200, 'text/plain', '<html></html>\n')
check_path('/static/index.html', 200, 'text/html', '<html></html>\n')
check_path('/static/foo.bar', 404, 'text/plain', 'Not Found')
- check_path('/static/test/index.html', 200, 'text/html',
- '<html></html>\n')
+ check_path(
+ '/static/test/index.html', 200, 'text/html', '<html></html>\n'
+ )
check_path('/bar/foo', 404, 'text/plain', 'Not Found')
check_path('', 404, 'text/plain', 'Not Found')
app.static_files[''] = 'index.html'
- check_path('/static/test/', 200, 'text/html',
- '<html></html>\n')
+ check_path('/static/test/', 200, 'text/html', '<html></html>\n')
app.static_files[''] = {'filename': 'index.html'}
- check_path('/static/test/', 200, 'text/html',
- '<html></html>\n')
+ check_path('/static/test/', 200, 'text/html', '<html></html>\n')
- app.static_files[''] = {'filename': 'index.html',
- 'content_type': 'image/gif'}
- check_path('/static/test/', 200, 'image/gif',
- '<html></html>\n')
+ app.static_files[''] = {
+ 'filename': 'index.html',
+ 'content_type': 'image/gif',
+ }
+ check_path('/static/test/', 200, 'image/gif', '<html></html>\n')
app.static_files[''] = {'filename': 'test.gif'}
check_path('/static/test/', 404, 'text/plain', 'Not Found')
@@ -110,11 +118,63 @@ class AsgiTests(unittest.TestCase):
def test_lifespan_startup(self):
app = async_asgi.ASGIApp('eio')
scope = {'type': 'lifespan'}
- receive = AsyncMock(return_value={'type': 'lifespan.startup'})
+ receive = AsyncMock(side_effect=[{'type': 'lifespan.startup'},
+ {'type': 'lifespan.shutdown'}])
send = AsyncMock()
_run(app(scope, receive, send))
- send.mock.assert_called_once_with(
- {'type': 'lifespan.startup.complete'})
+ send.mock.assert_any_call(
+ {'type': 'lifespan.startup.complete'}
+ )
+
+ def test_lifespan_startup_sync_function(self):
+ up = False
+
+ def startup():
+ nonlocal up
+ up = True
+
+ app = async_asgi.ASGIApp('eio', on_startup=startup)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(side_effect=[{'type': 'lifespan.startup'},
+ {'type': 'lifespan.shutdown'}])
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_any_call(
+ {'type': 'lifespan.startup.complete'}
+ )
+ assert up
+
+ def test_lifespan_startup_async_function(self):
+ up = False
+
+ async def startup():
+ nonlocal up
+ up = True
+
+ app = async_asgi.ASGIApp('eio', on_startup=startup)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(side_effect=[{'type': 'lifespan.startup'},
+ {'type': 'lifespan.shutdown'}])
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_any_call(
+ {'type': 'lifespan.startup.complete'}
+ )
+ assert up
+
+ def test_lifespan_startup_function_exception(self):
+ up = False
+
+ def startup():
+ raise Exception
+
+ app = async_asgi.ASGIApp('eio', on_startup=startup)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(side_effect=[{'type': 'lifespan.startup'}])
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_called_once_with({'type': 'lifespan.startup.failed'})
+ assert not up
def test_lifespan_shutdown(self):
app = async_asgi.ASGIApp('eio')
@@ -123,15 +183,67 @@ class AsgiTests(unittest.TestCase):
send = AsyncMock()
_run(app(scope, receive, send))
send.mock.assert_called_once_with(
- {'type': 'lifespan.shutdown.complete'})
+ {'type': 'lifespan.shutdown.complete'}
+ )
+
+ def test_lifespan_shutdown_sync_function(self):
+ down = False
+
+ def shutdown():
+ nonlocal down
+ down = True
+
+ app = async_asgi.ASGIApp('eio', on_shutdown=shutdown)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(return_value={'type': 'lifespan.shutdown'})
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_called_once_with(
+ {'type': 'lifespan.shutdown.complete'}
+ )
+ assert down
+
+ def test_lifespan_shutdown_async_function(self):
+ down = False
+
+ async def shutdown():
+ nonlocal down
+ down = True
+
+ app = async_asgi.ASGIApp('eio', on_shutdown=shutdown)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(return_value={'type': 'lifespan.shutdown'})
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_called_once_with(
+ {'type': 'lifespan.shutdown.complete'}
+ )
+ assert down
+
+ def test_lifespan_shutdown_function_exception(self):
+ down = False
+
+ def shutdown():
+ raise Exception
+
+ app = async_asgi.ASGIApp('eio', on_shutdown=shutdown)
+ scope = {'type': 'lifespan'}
+ receive = AsyncMock(return_value={'type': 'lifespan.shutdown'})
+ send = AsyncMock()
+ _run(app(scope, receive, send))
+ send.mock.assert_called_once_with({'type': 'lifespan.shutdown.failed'})
+ assert not down
def test_lifespan_invalid(self):
app = async_asgi.ASGIApp('eio')
scope = {'type': 'lifespan'}
- receive = AsyncMock(return_value={'type': 'lifespan.foo'})
+ receive = AsyncMock(side_effect=[{'type': 'lifespan.foo'},
+ {'type': 'lifespan.shutdown'}])
send = AsyncMock()
_run(app(scope, receive, send))
- send.mock.assert_not_called()
+ send.mock.assert_called_once_with(
+ {'type': 'lifespan.shutdown.complete'}
+ )
def test_not_found(self):
app = async_asgi.ASGIApp('eio')
@@ -140,23 +252,40 @@ class AsgiTests(unittest.TestCase):
send = AsyncMock()
_run(app(scope, receive, send))
send.mock.assert_any_call(
- {'type': 'http.response.start', 'status': 404,
- 'headers': [(b'Content-Type', b'text/plain')]})
- send.mock.assert_any_call({'type': 'http.response.body',
- 'body': b'Not Found'})
+ {
+ 'type': 'http.response.start',
+ 'status': 404,
+ 'headers': [(b'Content-Type', b'text/plain')],
+ }
+ )
+ send.mock.assert_any_call(
+ {'type': 'http.response.body', 'body': b'Not Found'}
+ )
def test_translate_request(self):
- receive = AsyncMock(return_value={'type': 'http.request',
- 'body': b'hello world'})
+ receive = AsyncMock(
+ return_value={'type': 'http.request', 'body': b'hello world'}
+ )
send = AsyncMock()
- environ = _run(async_asgi.translate_request({
- 'type': 'http',
- 'method': 'PUT',
- 'headers': [(b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'),
+ environ = _run(
+ async_asgi.translate_request(
+ {
+ 'type': 'http',
+ 'method': 'PUT',
+ 'headers': [
+ (b'a', b'b'),
+ (b'c-c', b'd'),
+ (b'c_c', b'e'),
(b'content-type', b'application/json'),
- (b'content-length', b'123')],
- 'path': '/foo/bar',
- 'query_string': b'baz=1'}, receive, send))
+ (b'content-length', b'123'),
+ ],
+ 'path': '/foo/bar',
+ 'query_string': b'baz=1',
+ },
+ receive,
+ send,
+ )
+ )
expected_environ = {
'REQUEST_METHOD': 'PUT',
'PATH_INFO': '/foo/bar',
@@ -168,26 +297,37 @@ class AsgiTests(unittest.TestCase):
'RAW_URI': '/foo/bar?baz=1',
'SERVER_PROTOCOL': 'HTTP/1.1',
'asgi.receive': receive,
- 'asgi.send': send
+ 'asgi.send': send,
}
for k, v in expected_environ.items():
- self.assertEqual(v, environ[k])
- self.assertTrue(
- environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d')
+ assert v == environ[k]
+ assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d'
body = _run(environ['wsgi.input'].read())
- self.assertEqual(body, b'hello world')
+ assert body == b'hello world'
def test_translate_request_no_query_string(self):
- receive = AsyncMock(return_value={'type': 'http.request',
- 'body': b'hello world'})
+ receive = AsyncMock(
+ return_value={'type': 'http.request', 'body': b'hello world'}
+ )
send = AsyncMock()
- environ = _run(async_asgi.translate_request({
- 'type': 'http',
- 'method': 'PUT',
- 'headers': [(b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'),
+ environ = _run(
+ async_asgi.translate_request(
+ {
+ 'type': 'http',
+ 'method': 'PUT',
+ 'headers': [
+ (b'a', b'b'),
+ (b'c-c', b'd'),
+ (b'c_c', b'e'),
(b'content-type', b'application/json'),
- (b'content-length', b'123')],
- 'path': '/foo/bar'}, receive, send))
+ (b'content-length', b'123'),
+ ],
+ 'path': '/foo/bar',
+ },
+ receive,
+ send,
+ )
+ )
expected_environ = {
'REQUEST_METHOD': 'PUT',
'PATH_INFO': '/foo/bar',
@@ -199,31 +339,43 @@ class AsgiTests(unittest.TestCase):
'RAW_URI': '/foo/bar',
'SERVER_PROTOCOL': 'HTTP/1.1',
'asgi.receive': receive,
- 'asgi.send': send
+ 'asgi.send': send,
}
for k, v in expected_environ.items():
- self.assertEqual(v, environ[k])
- self.assertTrue(
- environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d')
+ assert v == environ[k]
+ assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d'
body = _run(environ['wsgi.input'].read())
- self.assertEqual(body, b'hello world')
+ assert body == b'hello world'
def test_translate_request_with_large_body(self):
- receive = AsyncMock(side_effect=[
- {'type': 'http.request', 'body': b'hello ', 'more_body': True},
- {'type': 'http.request', 'body': b'world', 'more_body': True},
- {'type': 'foo.bar'}, # should stop parsing here
- {'type': 'http.request', 'body': b'!!!'},
- ])
+ receive = AsyncMock(
+ side_effect=[
+ {'type': 'http.request', 'body': b'hello ', 'more_body': True},
+ {'type': 'http.request', 'body': b'world', 'more_body': True},
+ {'type': 'foo.bar'}, # should stop parsing here
+ {'type': 'http.request', 'body': b'!!!'},
+ ]
+ )
send = AsyncMock()
- environ = _run(async_asgi.translate_request({
- 'type': 'http',
- 'method': 'PUT',
- 'headers': [(b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'),
+ environ = _run(
+ async_asgi.translate_request(
+ {
+ 'type': 'http',
+ 'method': 'PUT',
+ 'headers': [
+ (b'a', b'b'),
+ (b'c-c', b'd'),
+ (b'c_c', b'e'),
(b'content-type', b'application/json'),
- (b'content-length', b'123')],
- 'path': '/foo/bar',
- 'query_string': b'baz=1'}, receive, send))
+ (b'content-length', b'123'),
+ ],
+ 'path': '/foo/bar',
+ 'query_string': b'baz=1',
+ },
+ receive,
+ send,
+ )
+ )
expected_environ = {
'REQUEST_METHOD': 'PUT',
'PATH_INFO': '/foo/bar',
@@ -235,45 +387,91 @@ class AsgiTests(unittest.TestCase):
'RAW_URI': '/foo/bar?baz=1',
'SERVER_PROTOCOL': 'HTTP/1.1',
'asgi.receive': receive,
- 'asgi.send': send
+ 'asgi.send': send,
}
for k, v in expected_environ.items():
- self.assertEqual(v, environ[k])
- self.assertTrue(
- environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d')
+ assert v == environ[k]
+ assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d'
body = _run(environ['wsgi.input'].read())
- self.assertEqual(body, b'hello world')
+ assert body == b'hello world'
def test_translate_websocket_request(self):
receive = AsyncMock(return_value={'type': 'websocket.connect'})
send = AsyncMock()
- _run(async_asgi.translate_request({
- 'type': 'websocket',
- 'headers': [(b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'),
+ _run(
+ async_asgi.translate_request(
+ {
+ 'type': 'websocket',
+ 'headers': [
+ (b'a', b'b'),
+ (b'c-c', b'd'),
+ (b'c_c', b'e'),
(b'content-type', b'application/json'),
- (b'content-length', b'123')],
- 'path': '/foo/bar',
- 'query_string': b'baz=1'}, receive, send))
- send.mock.assert_called_once_with({'type': 'websocket.accept'})
+ (b'content-length', b'123'),
+ ],
+ 'path': '/foo/bar',
+ 'query_string': b'baz=1',
+ },
+ receive,
+ send,
+ )
+ )
+ send.mock.assert_not_called()
def test_translate_unknown_request(self):
receive = AsyncMock(return_value={'type': 'http.foo'})
send = AsyncMock()
- environ = _run(async_asgi.translate_request({
- 'type': 'http',
- 'path': '/foo/bar',
- 'query_string': b'baz=1'}, receive, send))
- self.assertEqual(environ, {})
+ environ = _run(
+ async_asgi.translate_request(
+ {'type': 'http', 'path': '/foo/bar', 'query_string': b'baz=1'},
+ receive,
+ send,
+ )
+ )
+ assert environ == {}
- # @mock.patch('async_aiohttp.aiohttp.web.Response')
def test_make_response(self):
- environ = {
- 'asgi.send': AsyncMock()
- }
- _run(async_asgi.make_response('202 ACCEPTED', [('foo', 'bar')],
- b'payload', environ))
+ environ = {'asgi.send': AsyncMock()}
+ _run(
+ async_asgi.make_response(
+ '202 ACCEPTED', [('foo', 'bar')], b'payload', environ
+ )
+ )
environ['asgi.send'].mock.assert_any_call(
- {'type': 'http.response.start', 'status': 202,
- 'headers': [(b'foo', b'bar')]})
+ {
+ 'type': 'http.response.start',
+ 'status': 202,
+ 'headers': [(b'foo', b'bar')],
+ }
+ )
environ['asgi.send'].mock.assert_any_call(
- {'type': 'http.response.body', 'body': b'payload'})
+ {'type': 'http.response.body', 'body': b'payload'}
+ )
+
+ def test_make_response_websocket_accept(self):
+ environ = {
+ 'asgi.send': AsyncMock(),
+ 'HTTP_SEC_WEBSOCKET_VERSION': 'foo',
+ }
+ _run(
+ async_asgi.make_response(
+ '200 OK', [('foo', 'bar')], b'payload', environ
+ )
+ )
+ environ['asgi.send'].mock.assert_called_with(
+ {'type': 'websocket.accept', 'headers': [(b'foo', b'bar')]}
+ )
+
+ def test_make_response_websocket_reject(self):
+ environ = {
+ 'asgi.send': AsyncMock(),
+ 'HTTP_SEC_WEBSOCKET_VERSION': 'foo',
+ }
+ _run(
+ async_asgi.make_response(
+ '401 UNAUTHORIZED', [('foo', 'bar')], b'payload', environ
+ )
+ )
+ environ['asgi.send'].mock.assert_called_with(
+ {'type': 'websocket.close'}
+ )
diff --git a/debian/tests/asyncio/test_async_sanic.py b/debian/tests/asyncio/test_async_sanic.py
new file mode 100644
index 0000000..a1aa7de
--- /dev/null
+++ b/debian/tests/asyncio/test_async_sanic.py
@@ -0,0 +1 @@
+from engineio.async_drivers import sanic as async_sanic # noqa: F401
diff --git a/debian/tests/asyncio/test_async_tornado.py b/debian/tests/asyncio/test_async_tornado.py
index dde2186..e6a8a3c 100644
--- a/debian/tests/asyncio/test_async_tornado.py
+++ b/debian/tests/asyncio/test_async_tornado.py
@@ -1,22 +1,14 @@
-try:
- import asyncio
-except ImportError:
- pass
+import asyncio
import sys
import unittest
+from unittest import mock
-import six
try:
import tornado.web
except ImportError:
pass
-if six.PY3:
- from unittest import mock
-else:
- import mock
-if sys.version_info >= (3, 5):
- from engineio.async_drivers import tornado as async_tornado
+from engineio.async_drivers import tornado as async_tornado
def _run(coro):
@@ -29,8 +21,7 @@ class TornadoTests(unittest.TestCase):
def test_get_tornado_handler(self):
mock_server = mock.MagicMock()
handler = async_tornado.get_tornado_handler(mock_server)
- self.assertTrue(issubclass(handler,
- tornado.websocket.WebSocketHandler))
+ assert issubclass(handler, tornado.websocket.WebSocketHandler)
def test_translate_request(self):
mock_handler = mock.MagicMock()
@@ -42,7 +33,7 @@ class TornadoTests(unittest.TestCase):
'a': 'b',
'c': 'd',
'content-type': 'application/json',
- 'content-length': 123
+ 'content-length': 123,
}
mock_handler.request.body = b'hello world'
environ = async_tornado.translate_request(mock_handler)
@@ -60,16 +51,17 @@ class TornadoTests(unittest.TestCase):
'tornado.handler': mock_handler,
}
for k, v in expected_environ.items():
- self.assertEqual(v, environ[k])
+ assert v == environ[k]
payload = _run(environ['wsgi.input'].read(1))
payload += _run(environ['wsgi.input'].read())
- self.assertEqual(payload, b'hello world')
+ assert payload == b'hello world'
def test_make_response(self):
mock_handler = mock.MagicMock()
mock_environ = {'tornado.handler': mock_handler}
- async_tornado.make_response('202 ACCEPTED', [('foo', 'bar')],
- b'payload', mock_environ)
+ async_tornado.make_response(
+ '202 ACCEPTED', [('foo', 'bar')], b'payload', mock_environ
+ )
mock_handler.set_status.assert_called_once_with(202)
mock_handler.set_header.assert_called_once_with('foo', 'bar')
mock_handler.write.assert_called_once_with(b'payload')
diff --git a/debian/tests/asyncio/test_asyncio_client.py b/debian/tests/asyncio/test_asyncio_client.py
index 236e90d..bea49e1 100644
--- a/debian/tests/asyncio/test_asyncio_client.py
+++ b/debian/tests/asyncio/test_asyncio_client.py
@@ -1,18 +1,14 @@
import asyncio
import ssl
import sys
-import time
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
try:
import aiohttp
except ImportError:
aiohttp = None
+import pytest
from engineio import asyncio_client
from engineio import client
@@ -41,79 +37,80 @@ def _run(coro):
class TestAsyncClient(unittest.TestCase):
def test_is_asyncio_based(self):
c = asyncio_client.AsyncClient()
- self.assertEqual(c.is_asyncio_based(), True)
+ assert c.is_asyncio_based()
def test_already_connected(self):
c = asyncio_client.AsyncClient()
c.state = 'connected'
- self.assertRaises(ValueError, _run, c.connect('http://foo'))
+ with pytest.raises(ValueError):
+ _run(c.connect('http://foo'))
def test_invalid_transports(self):
c = asyncio_client.AsyncClient()
- self.assertRaises(ValueError, _run, c.connect(
- 'http://foo', transports=['foo', 'bar']))
+ with pytest.raises(ValueError):
+ _run(c.connect('http://foo', transports=['foo', 'bar']))
def test_some_invalid_transports(self):
c = asyncio_client.AsyncClient()
c._connect_websocket = AsyncMock()
_run(c.connect('http://foo', transports=['foo', 'websocket', 'bar']))
- self.assertEqual(c.transports, ['websocket'])
+ assert c.transports == ['websocket']
def test_connect_polling(self):
c = asyncio_client.AsyncClient()
c._connect_polling = AsyncMock(return_value='foo')
- self.assertEqual(_run(c.connect('http://foo')), 'foo')
+ assert _run(c.connect('http://foo')) == 'foo'
c._connect_polling.mock.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = asyncio_client.AsyncClient()
c._connect_polling = AsyncMock(return_value='foo')
- self.assertEqual(
- _run(c.connect('http://foo', transports=['polling'])), 'foo')
+ assert _run(c.connect('http://foo', transports=['polling'])) == 'foo'
c._connect_polling.mock.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = asyncio_client.AsyncClient()
c._connect_polling = AsyncMock(return_value='foo')
- self.assertEqual(
- _run(c.connect('http://foo', transports=['polling',
- 'websocket'])),
- 'foo')
+ assert (
+ _run(c.connect('http://foo', transports=['polling', 'websocket']))
+ == 'foo'
+ )
c._connect_polling.mock.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
def test_connect_websocket(self):
c = asyncio_client.AsyncClient()
c._connect_websocket = AsyncMock(return_value='foo')
- self.assertEqual(
- _run(c.connect('http://foo', transports=['websocket'])),
- 'foo')
+ assert _run(c.connect('http://foo', transports=['websocket'])) == 'foo'
c._connect_websocket.mock.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = asyncio_client.AsyncClient()
c._connect_websocket = AsyncMock(return_value='foo')
- self.assertEqual(
- _run(c.connect('http://foo', transports='websocket')),
- 'foo')
+ assert _run(c.connect('http://foo', transports='websocket')) == 'foo'
c._connect_websocket.mock.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
def test_connect_query_string(self):
c = asyncio_client.AsyncClient()
c._connect_polling = AsyncMock(return_value='foo')
- self.assertEqual(_run(c.connect('http://foo?bar=baz')), 'foo')
+ assert _run(c.connect('http://foo?bar=baz')) == 'foo'
c._connect_polling.mock.assert_called_once_with(
- 'http://foo?bar=baz', {}, 'engine.io')
+ 'http://foo?bar=baz', {}, 'engine.io'
+ )
def test_connect_custom_headers(self):
c = asyncio_client.AsyncClient()
c._connect_polling = AsyncMock(return_value='foo')
- self.assertEqual(
- _run(c.connect('http://foo', headers={'Foo': 'Bar'})),
- 'foo')
+ assert _run(c.connect('http://foo', headers={'Foo': 'Bar'})) == 'foo'
c._connect_polling.mock.assert_called_once_with(
- 'http://foo', {'Foo': 'Bar'}, 'engine.io')
+ 'http://foo', {'Foo': 'Bar'}, 'engine.io'
+ )
def test_wait(self):
c = asyncio_client.AsyncClient()
@@ -124,7 +121,7 @@ class TestAsyncClient(unittest.TestCase):
c.read_loop_task = fake_read_look_task()
_run(c.wait())
- self.assertEqual(done, [True])
+ assert done == [True]
def test_wait_no_task(self):
c = asyncio_client.AsyncClient()
@@ -140,26 +137,25 @@ class TestAsyncClient(unittest.TestCase):
c._send_packet = fake_send_packet
_run(c.send('foo'))
- _run(c.send('foo', binary=False))
- _run(c.send(b'foo', binary=True))
- self.assertEqual(saved_packets[0].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[0].data, 'foo')
- self.assertEqual(saved_packets[0].binary,
- False if six.PY3 else True)
- self.assertEqual(saved_packets[1].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[1].data, 'foo')
- self.assertEqual(saved_packets[1].binary, False)
- self.assertEqual(saved_packets[2].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[2].data, b'foo')
- self.assertEqual(saved_packets[2].binary, True)
+ _run(c.send('foo'))
+ _run(c.send(b'foo'))
+ assert saved_packets[0].packet_type == packet.MESSAGE
+ assert saved_packets[0].data == 'foo'
+ assert not saved_packets[0].binary
+ assert saved_packets[1].packet_type == packet.MESSAGE
+ assert saved_packets[1].data == 'foo'
+ assert not saved_packets[1].binary
+ assert saved_packets[2].packet_type == packet.MESSAGE
+ assert saved_packets[2].data == b'foo'
+ assert saved_packets[2].binary
def test_disconnect_not_connected(self):
c = asyncio_client.AsyncClient()
c.state = 'foo'
c.sid = 'bar'
_run(c.disconnect())
- self.assertEqual(c.state, 'disconnected')
- self.assertIsNone(c.sid)
+ assert c.state == 'disconnected'
+ assert c.sid is None
def test_disconnect_polling(self):
c = asyncio_client.AsyncClient()
@@ -175,9 +171,10 @@ class TestAsyncClient(unittest.TestCase):
c._trigger_event = AsyncMock()
_run(c.disconnect())
c.ws.close.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
- c._trigger_event.mock.assert_called_once_with('disconnect',
- run_async=False)
+ assert c not in client.connected_clients
+ c._trigger_event.mock.assert_called_once_with(
+ 'disconnect', run_async=False
+ )
def test_disconnect_websocket(self):
c = asyncio_client.AsyncClient()
@@ -193,9 +190,10 @@ class TestAsyncClient(unittest.TestCase):
c._trigger_event = AsyncMock()
_run(c.disconnect())
c.ws.close.mock.assert_called_once_with()
- self.assertNotIn(c, client.connected_clients)
- c._trigger_event.mock.assert_called_once_with('disconnect',
- run_async=False)
+ assert c not in client.connected_clients
+ c._trigger_event.mock.assert_called_once_with(
+ 'disconnect', run_async=False
+ )
def test_disconnect_polling_abort(self):
c = asyncio_client.AsyncClient()
@@ -211,7 +209,7 @@ class TestAsyncClient(unittest.TestCase):
_run(c.disconnect(abort=True))
c.queue.join.mock.assert_not_called()
c.ws.close.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
+ assert c not in client.connected_clients
def test_disconnect_websocket_abort(self):
c = asyncio_client.AsyncClient()
@@ -227,7 +225,7 @@ class TestAsyncClient(unittest.TestCase):
_run(c.disconnect(abort=True))
c.queue.join.mock.assert_not_called()
c.ws.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
+ assert c not in client.connected_clients
def test_background_tasks(self):
r = []
@@ -237,9 +235,10 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.start_background_task(foo, 'bar')
- pending = asyncio.Task.all_tasks()
+ pending = asyncio.all_tasks(loop=asyncio.get_event_loop()) \
+ if hasattr(asyncio, 'all_tasks') else asyncio.Task.all_tasks()
asyncio.get_event_loop().run_until_complete(asyncio.wait(pending))
- self.assertEqual(r, ['bar'])
+ assert r == ['bar']
def test_sleep(self):
c = asyncio_client.AsyncClient()
@@ -248,139 +247,193 @@ class TestAsyncClient(unittest.TestCase):
def test_create_queue(self):
c = asyncio_client.AsyncClient()
q = c.create_queue()
- self.assertRaises(q.Empty, q.get_nowait)
+ with pytest.raises(q.Empty):
+ q.get_nowait()
def test_create_event(self):
c = asyncio_client.AsyncClient()
e = c.create_event()
- self.assertFalse(e.is_set())
+ assert not e.is_set()
e.set()
- self.assertTrue(e.is_set())
+ assert e.is_set()
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_polling_connection_failed(self, _time):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock(return_value=None)
- self.assertRaises(
- exceptions.ConnectionError, _run, c.connect(
- 'http://foo', headers={'Foo': 'Bar'}))
+ with pytest.raises(exceptions.ConnectionError):
+ _run(c.connect('http://foo', headers={'Foo': 'Bar'}))
c._send_request.mock.assert_called_once_with(
- 'GET', 'http://foo/engine.io/?transport=polling&EIO=3&t=123.456',
- headers={'Foo': 'Bar'}, timeout=5)
+ 'GET',
+ 'http://foo/engine.io/?transport=polling&EIO=4&t=123.456',
+ headers={'Foo': 'Bar'},
+ timeout=5,
+ )
def test_polling_connection_404(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 404
- self.assertRaises(
- exceptions.ConnectionError, _run, c.connect('http://foo'))
+ c._send_request.mock.return_value.json = AsyncMock(
+ return_value={'foo': 'bar'}
+ )
+ try:
+ _run(c.connect('http://foo'))
+ except exceptions.ConnectionError as exc:
+ assert len(exc.args) == 2
+ assert (
+ exc.args[0] == 'Unexpected status code 404 in server response'
+ )
+ assert exc.args[1] == {'foo': 'bar'}
+
+ def test_polling_connection_404_no_json(self):
+ c = asyncio_client.AsyncClient()
+ c._send_request = AsyncMock()
+ c._send_request.mock.return_value.status = 404
+ c._send_request.mock.return_value.json = AsyncMock(
+ side_effect=aiohttp.ContentTypeError('foo', 'bar')
+ )
+ try:
+ _run(c.connect('http://foo'))
+ except exceptions.ConnectionError as exc:
+ assert len(exc.args) == 2
+ assert (
+ exc.args[0] == 'Unexpected status code 404 in server response'
+ )
+ assert exc.args[1] is None
def test_polling_connection_invalid_packet(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
- c._send_request.mock.return_value.read = AsyncMock(
- return_value=b'foo')
- self.assertRaises(
- exceptions.ConnectionError, _run, c.connect('http://foo'))
+ c._send_request.mock.return_value.read = AsyncMock(return_value=b'foo')
+ with pytest.raises(exceptions.ConnectionError):
+ _run(c.connect('http://foo'))
def test_polling_connection_no_open_packet(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.CLOSE, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 10,
- 'pingTimeout': 20
- })
- ]).encode())
- self.assertRaises(
- exceptions.ConnectionError, _run, c.connect('http://foo'))
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.CLOSE,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 10,
+ 'pingTimeout': 20,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ )
+ with pytest.raises(exceptions.ConnectionError):
+ _run(c.connect('http://foo'))
def test_polling_connection_successful(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode())
- c._ping_loop = AsyncMock()
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ )
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = AsyncMock()
c.on('connect', on_connect)
_run(c.connect('http://foo'))
- time.sleep(0.1)
- c._ping_loop.mock.assert_called_once_with()
c._read_loop_polling.mock.assert_called_once_with()
c._read_loop_websocket.mock.assert_not_called()
c._write_loop.mock.assert_called_once_with()
on_connect.mock.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'polling')
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'polling'
def test_polling_https_noverify_connection_successful(self):
c = asyncio_client.AsyncClient(ssl_verify=False)
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode())
- c._ping_loop = AsyncMock()
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ )
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = AsyncMock()
c.on('connect', on_connect)
_run(c.connect('https://foo'))
- time.sleep(0.1)
- c._ping_loop.mock.assert_called_once_with()
c._read_loop_polling.mock.assert_called_once_with()
c._read_loop_websocket.mock.assert_not_called()
c._write_loop.mock.assert_called_once_with()
on_connect.mock.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'https://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'polling')
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'https://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'polling'
def test_polling_connection_with_more_packets(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }),
- packet.Packet(packet.NOOP)
- ]).encode())
- c._ping_loop = AsyncMock()
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ),
+ packet.Packet(packet.NOOP),
+ ]
+ ).encode().encode('utf-8')
+ )
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
@@ -388,96 +441,119 @@ class TestAsyncClient(unittest.TestCase):
on_connect = AsyncMock()
c.on('connect', on_connect)
_run(c.connect('http://foo'))
- time.sleep(0.1)
- self.assertEqual(c._receive_packet.mock.call_count, 1)
- self.assertEqual(
- c._receive_packet.mock.call_args_list[0][0][0].packet_type,
- packet.NOOP)
+ assert c._receive_packet.mock.call_count == 1
+ assert (
+ c._receive_packet.mock.call_args_list[0][0][0].packet_type
+ == packet.NOOP
+ )
def test_polling_connection_upgraded(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': ['websocket'],
- 'pingInterval': 1000, 'pingTimeout': 2000
- })
- ]).encode())
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': ['websocket'],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ )
c._connect_websocket = AsyncMock(return_value=True)
on_connect = mock.MagicMock()
c.on('connect', on_connect)
_run(c.connect('http://foo'))
- c._connect_websocket.mock.assert_called_once_with('http://foo', {},
- 'engine.io')
+ c._connect_websocket.mock.assert_called_once_with(
+ 'http://foo', {}, 'engine.io'
+ )
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, ['websocket'])
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == ['websocket']
def test_polling_connection_not_upgraded(self):
c = asyncio_client.AsyncClient()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
c._send_request.mock.return_value.read = AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': ['websocket'],
- 'pingInterval': 1000, 'pingTimeout': 2000
- })
- ]).encode())
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': ['websocket'],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ )
c._connect_websocket = AsyncMock(return_value=False)
- c._ping_loop = AsyncMock()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
_run(c.connect('http://foo'))
- time.sleep(0.1)
- c._connect_websocket.mock.assert_called_once_with('http://foo', {},
- 'engine.io')
- c._ping_loop.mock.assert_called_once_with()
+ c._connect_websocket.mock.assert_called_once_with(
+ 'http://foo', {}, 'engine.io'
+ )
c._read_loop_polling.mock.assert_called_once_with()
c._read_loop_websocket.mock.assert_not_called()
c._write_loop.mock.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
+ assert c in client.connected_clients
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_websocket_connection_failed(self, _time):
c = asyncio_client.AsyncClient()
c.http = mock.MagicMock(closed=False)
- c.http.ws_connect = AsyncMock(side_effect=[
- aiohttp.client_exceptions.ServerConnectionError()])
- self.assertRaises(
- exceptions.ConnectionError, _run,
- c.connect('http://foo', transports=['websocket'],
- headers={'Foo': 'Bar'}))
+ c.http.ws_connect = AsyncMock(
+ side_effect=[aiohttp.client_exceptions.ServerConnectionError()]
+ )
+ with pytest.raises(exceptions.ConnectionError):
+ _run(
+ c.connect(
+ 'http://foo',
+ transports=['websocket'],
+ headers={'Foo': 'Bar'},
+ )
+ )
c.http.ws_connect.mock.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&t=123.456',
- headers={'Foo': 'Bar'})
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ headers={'Foo': 'Bar'},
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_websocket_upgrade_failed(self, _time):
c = asyncio_client.AsyncClient()
c.http = mock.MagicMock(closed=False)
- c.http.ws_connect = AsyncMock(side_effect=[
- aiohttp.client_exceptions.ServerConnectionError()])
+ c.http.ws_connect = AsyncMock(
+ side_effect=[aiohttp.client_exceptions.ServerConnectionError()]
+ )
c.sid = '123'
- self.assertFalse(_run(c.connect(
- 'http://foo', transports=['websocket'])))
+ assert not _run(c.connect('http://foo', transports=['websocket']))
c.http.ws_connect.mock.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&sid=123&t=123.456',
- headers={})
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&sid=123&t=123.456',
+ headers={},
+ )
def test_websocket_connection_no_open_packet(self):
c = asyncio_client.AsyncClient()
@@ -486,10 +562,10 @@ class TestAsyncClient(unittest.TestCase):
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
ws.receive.mock.return_value.data = packet.Packet(
- packet.CLOSE).encode()
- self.assertRaises(
- exceptions.ConnectionError, _run,
- c.connect('http://foo', transports=['websocket']))
+ packet.CLOSE
+ ).encode()
+ with pytest.raises(exceptions.ConnectionError):
+ _run(c.connect('http://foo', transports=['websocket']))
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_websocket_connection_successful(self, _time):
@@ -498,37 +574,38 @@ class TestAsyncClient(unittest.TestCase):
c.http.ws_connect = AsyncMock()
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
- ws.receive.mock.return_value.data = packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
- c._ping_loop = AsyncMock()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
_run(c.connect('ws://foo', transports=['websocket']))
- time.sleep(0.1)
- c._ping_loop.mock.assert_called_once_with()
c._read_loop_polling.mock.assert_not_called()
c._read_loop_websocket.mock.assert_called_once_with()
c._write_loop.mock.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'ws://foo/engine.io/?transport=websocket&EIO=3')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, ws)
+ assert c in client.connected_clients
+ assert c.base_url == 'ws://foo/engine.io/?transport=websocket&EIO=4'
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'websocket'
+ assert c.ws == ws
c.http.ws_connect.mock.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&t=123.456',
- headers={})
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ headers={},
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_websocket_https_noverify_connection_successful(self, _time):
@@ -537,38 +614,38 @@ class TestAsyncClient(unittest.TestCase):
c.http.ws_connect = AsyncMock()
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
- ws.receive.mock.return_value.data = packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
- c._ping_loop = AsyncMock()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
_run(c.connect('wss://foo', transports=['websocket']))
- time.sleep(0.1)
- c._ping_loop.mock.assert_called_once_with()
c._read_loop_polling.mock.assert_not_called()
c._read_loop_websocket.mock.assert_called_once_with()
c._write_loop.mock.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'wss://foo/engine.io/?transport=websocket&EIO=3')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, ws)
+ assert c in client.connected_clients
+ assert c.base_url == 'wss://foo/engine.io/?transport=websocket&EIO=4'
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'websocket'
+ assert c.ws == ws
_, kwargs = c.http.ws_connect.mock.call_args
- self.assertTrue('ssl' in kwargs)
- self.assertTrue(isinstance(kwargs['ssl'], ssl.SSLContext))
- self.assertEqual(kwargs['ssl'].verify_mode, ssl.CERT_NONE)
+ assert 'ssl' in kwargs
+ assert isinstance(kwargs['ssl'], ssl.SSLContext)
+ assert kwargs['ssl'].verify_mode == ssl.CERT_NONE
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_websocket_connection_with_cookies(self, _time):
@@ -577,26 +654,108 @@ class TestAsyncClient(unittest.TestCase):
c.http.ws_connect = AsyncMock()
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
- ws.receive.mock.return_value.data = packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c.http._cookie_jar = [mock.MagicMock(), mock.MagicMock()]
c.http._cookie_jar[0].key = 'key'
c.http._cookie_jar[0].value = 'value'
c.http._cookie_jar[1].key = 'key2'
c.http._cookie_jar[1].value = 'value2'
- c._ping_loop = AsyncMock()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
_run(c.connect('ws://foo', transports=['websocket']))
- time.sleep(0.1)
c.http.ws_connect.mock.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&t=123.456',
- headers={})
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ headers={},
+ )
+
+ @mock.patch('engineio.client.time.time', return_value=123.456)
+ def test_websocket_connection_with_cookie_header(self, _time):
+ c = asyncio_client.AsyncClient()
+ c.http = mock.MagicMock(closed=False)
+ c.http.ws_connect = AsyncMock()
+ ws = c.http.ws_connect.mock.return_value
+ ws.receive = AsyncMock()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ c.http._cookie_jar = []
+ c._read_loop_polling = AsyncMock()
+ c._read_loop_websocket = AsyncMock()
+ c._write_loop = AsyncMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ _run(
+ c.connect(
+ 'ws://foo',
+ headers={'Cookie': 'key=value; key2=value2; key3="value3="'},
+ transports=['websocket'],
+ )
+ )
+ c.http.ws_connect.mock.assert_called_once_with(
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ headers={},
+ )
+ c.http.cookie_jar.update_cookies.assert_called_once_with(
+ {'key': 'value', 'key2': 'value2', 'key3': '"value3="'}
+ )
+
+ @mock.patch('engineio.client.time.time', return_value=123.456)
+ def test_websocket_connection_with_cookies_and_headers(self, _time):
+ c = asyncio_client.AsyncClient()
+ c.http = mock.MagicMock(closed=False)
+ c.http.ws_connect = AsyncMock()
+ ws = c.http.ws_connect.mock.return_value
+ ws.receive = AsyncMock()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ c.http._cookie_jar = [mock.MagicMock(), mock.MagicMock()]
+ c.http._cookie_jar[0].key = 'key'
+ c.http._cookie_jar[0].value = 'value'
+ c.http._cookie_jar[1].key = 'key2'
+ c.http._cookie_jar[1].value = 'value2'
+ c._read_loop_polling = AsyncMock()
+ c._read_loop_websocket = AsyncMock()
+ c._write_loop = AsyncMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ _run(
+ c.connect(
+ 'ws://foo',
+ headers={'Foo': 'Bar', 'Cookie': 'key3=value3'},
+ transports=['websocket'],
+ )
+ )
+ c.http.ws_connect.mock.assert_called_once_with(
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ headers={'Foo': 'Bar'},
+ )
+ c.http.cookie_jar.update_cookies.assert_called_once_with(
+ {'key3': 'value3'}
+ )
def test_websocket_upgrade_no_pong(self):
c = asyncio_client.AsyncClient()
@@ -604,28 +763,30 @@ class TestAsyncClient(unittest.TestCase):
c.http.ws_connect = AsyncMock()
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
- ws.receive.mock.return_value.data = packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
+ ws.receive.mock.return_value.data = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
ws.send_str = AsyncMock()
c.sid = '123'
c.current_transport = 'polling'
- c._ping_loop = AsyncMock()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
- self.assertFalse(_run(c.connect('ws://foo',
- transports=['websocket'])))
+ assert not _run(c.connect('ws://foo', transports=['websocket']))
- c._ping_loop.mock.assert_not_called()
c._read_loop_polling.mock.assert_not_called()
c._read_loop_websocket.mock.assert_not_called()
c._write_loop.mock.assert_not_called()
on_connect.assert_not_called()
- self.assertEqual(c.transport(), 'polling')
+ assert c.transport() == 'polling'
ws.send_str.mock.assert_called_once_with('2probe')
def test_websocket_upgrade_successful(self):
@@ -635,41 +796,34 @@ class TestAsyncClient(unittest.TestCase):
ws = c.http.ws_connect.mock.return_value
ws.receive = AsyncMock()
ws.receive.mock.return_value.data = packet.Packet(
- packet.PONG, 'probe').encode()
+ packet.PONG, 'probe'
+ ).encode()
ws.send_str = AsyncMock()
c.sid = '123'
c.base_url = 'http://foo'
c.current_transport = 'polling'
- c._ping_loop = AsyncMock()
c._read_loop_polling = AsyncMock()
c._read_loop_websocket = AsyncMock()
c._write_loop = AsyncMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
- self.assertTrue(_run(c.connect('ws://foo',
- transports=['websocket'])))
- time.sleep(0.1)
+ assert _run(c.connect('ws://foo', transports=['websocket']))
- c._ping_loop.mock.assert_called_once_with()
c._read_loop_polling.mock.assert_not_called()
c._read_loop_websocket.mock.assert_called_once_with()
c._write_loop.mock.assert_called_once_with()
on_connect.assert_not_called() # was called by polling
- self.assertNotIn(c, client.connected_clients) # was added by polling
- self.assertEqual(c.base_url, 'http://foo') # not changed
- self.assertEqual(c.sid, '123') # not changed
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, ws)
- self.assertEqual(
- ws.send_str.mock.call_args_list[0],
- (('2probe',),)) # ping
- self.assertEqual(
- ws.send_str.mock.call_args_list[1],
- (('5',),)) # upgrade
+ assert c not in client.connected_clients # was added by polling
+ assert c.base_url == 'http://foo' # not changed
+ assert c.sid == '123' # not changed
+ assert c.transport() == 'websocket'
+ assert c.ws == ws
+ assert ws.send_str.mock.call_args_list[0] == (('2probe',),) # ping
+ assert ws.send_str.mock.call_args_list[1] == (('5',),) # upgrade
def test_receive_unknown_packet(self):
c = asyncio_client.AsyncClient()
- _run(c._receive_packet(packet.Packet(encoded_packet=b'9')))
+ _run(c._receive_packet(packet.Packet(encoded_packet='9')))
# should be ignored
def test_receive_noop_packet(self):
@@ -677,18 +831,19 @@ class TestAsyncClient(unittest.TestCase):
_run(c._receive_packet(packet.Packet(packet.NOOP)))
# should be ignored
- def test_receive_pong_packet(self):
+ def test_receive_ping_packet(self):
c = asyncio_client.AsyncClient()
- c.pong_received = False
- _run(c._receive_packet(packet.Packet(packet.PONG)))
- self.assertTrue(c.pong_received)
+ c._send_packet = AsyncMock()
+ _run(c._receive_packet(packet.Packet(packet.PING)))
+ assert c._send_packet.mock.call_args_list[0][0][0].encode() == '3'
def test_receive_message_packet(self):
c = asyncio_client.AsyncClient()
c._trigger_event = AsyncMock()
_run(c._receive_packet(packet.Packet(packet.MESSAGE, {'foo': 'bar'})))
c._trigger_event.mock.assert_called_once_with(
- 'message', {'foo': 'bar'}, run_async=True)
+ 'message', {'foo': 'bar'}, run_async=True
+ )
def test_receive_close_packet(self):
c = asyncio_client.AsyncClient()
@@ -701,16 +856,16 @@ class TestAsyncClient(unittest.TestCase):
c.queue = c.create_queue()
c.state = 'disconnected'
_run(c._send_packet(packet.Packet(packet.NOOP)))
- self.assertTrue(c.queue.empty())
+ assert c.queue.empty()
def test_send_packet(self):
c = asyncio_client.AsyncClient()
c.queue = c.create_queue()
c.state = 'connected'
_run(c._send_packet(packet.Packet(packet.NOOP)))
- self.assertFalse(c.queue.empty())
+ assert not c.queue.empty()
pkt = _run(c.queue.get())
- self.assertEqual(pkt.packet_type, packet.NOOP)
+ assert pkt.packet_type == packet.NOOP
def test_trigger_event_function(self):
result = []
@@ -722,7 +877,7 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('message', handler=foo_handler)
_run(c._trigger_event('message', 'bar'))
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_coroutine(self):
result = []
@@ -734,7 +889,7 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('message', handler=foo_handler)
_run(c._trigger_event('message', 'bar'))
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_function_error(self):
def connect_handler(arg):
@@ -746,8 +901,8 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('connect', handler=connect_handler)
c.on('message', handler=foo_handler)
- self.assertFalse(_run(c._trigger_event('connect', '123')))
- self.assertIsNone(_run(c._trigger_event('message', 'bar')))
+ assert not _run(c._trigger_event('connect', '123'))
+ assert _run(c._trigger_event('message', 'bar')) is None
def test_trigger_event_coroutine_error(self):
async def connect_handler(arg):
@@ -759,8 +914,8 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('connect', handler=connect_handler)
c.on('message', handler=foo_handler)
- self.assertFalse(_run(c._trigger_event('connect', '123')))
- self.assertIsNone(_run(c._trigger_event('message', 'bar')))
+ assert not _run(c._trigger_event('connect', '123'))
+ assert _run(c._trigger_event('message', 'bar')) is None
def test_trigger_event_function_async(self):
result = []
@@ -773,7 +928,7 @@ class TestAsyncClient(unittest.TestCase):
c.on('message', handler=foo_handler)
fut = _run(c._trigger_event('message', 'bar', run_async=True))
asyncio.get_event_loop().run_until_complete(fut)
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_coroutine_async(self):
result = []
@@ -786,7 +941,7 @@ class TestAsyncClient(unittest.TestCase):
c.on('message', handler=foo_handler)
fut = _run(c._trigger_event('message', 'bar', run_async=True))
asyncio.get_event_loop().run_until_complete(fut)
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_function_async_error(self):
result = []
@@ -798,10 +953,9 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('message', handler=foo_handler)
fut = _run(c._trigger_event('message', 'bar', run_async=True))
- self.assertRaises(
- ZeroDivisionError, asyncio.get_event_loop().run_until_complete,
- fut)
- self.assertEqual(result, ['bar'])
+ with pytest.raises(ZeroDivisionError):
+ asyncio.get_event_loop().run_until_complete(fut)
+ assert result == ['bar']
def test_trigger_event_coroutine_async_error(self):
result = []
@@ -813,10 +967,9 @@ class TestAsyncClient(unittest.TestCase):
c = asyncio_client.AsyncClient()
c.on('message', handler=foo_handler)
fut = _run(c._trigger_event('message', 'bar', run_async=True))
- self.assertRaises(
- ZeroDivisionError, asyncio.get_event_loop().run_until_complete,
- fut)
- self.assertEqual(result, ['bar'])
+ with pytest.raises(ZeroDivisionError):
+ asyncio.get_event_loop().run_until_complete(fut)
+ assert result == ['bar']
def test_trigger_unknown_event(self):
c = asyncio_client.AsyncClient()
@@ -824,82 +977,11 @@ class TestAsyncClient(unittest.TestCase):
_run(c._trigger_event('message', 123, run_async=True))
# should do nothing
- def test_ping_loop_disconnected(self):
- c = asyncio_client.AsyncClient()
- c.state = 'disconnected'
- _run(c._ping_loop())
- # should not block
-
- def test_ping_loop_disconnect(self):
- c = asyncio_client.AsyncClient()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = AsyncMock()
-
- states = [
- ('disconnecting', True)
- ]
-
- async def fake_wait():
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- _run(c._ping_loop())
- self.assertEqual(
- c._send_packet.mock.call_args_list[0][0][0].encode(), b'2')
-
- def test_ping_loop_missing_pong(self):
- c = asyncio_client.AsyncClient()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = AsyncMock()
- c.queue = mock.MagicMock()
- c.queue.put = AsyncMock()
-
- states = [
- ('connected', False)
- ]
-
- async def fake_wait():
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- _run(c._ping_loop())
- self.assertEqual(c.state, 'connected')
- c.queue.put.mock.assert_called_once_with(None)
-
- def test_ping_loop_missing_pong_websocket(self):
- c = asyncio_client.AsyncClient()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = AsyncMock()
- c.queue = mock.MagicMock()
- c.queue.put = AsyncMock()
- c.ws = mock.MagicMock()
- c.ws.close = AsyncMock()
-
- states = [
- ('connected', False)
- ]
-
- async def fake_wait():
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- _run(c._ping_loop())
- self.assertEqual(c.state, 'connected')
- c.queue.put.mock.assert_called_once_with(None)
- c.ws.close.mock.assert_called_once_with()
-
def test_read_loop_polling_disconnected(self):
c = asyncio_client.AsyncClient()
c.state = 'disconnected'
c._trigger_event = AsyncMock()
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_polling())
c._trigger_event.mock.assert_not_called()
# should not block
@@ -916,14 +998,15 @@ class TestAsyncClient(unittest.TestCase):
c._send_request = AsyncMock(return_value=None)
c._trigger_event = AsyncMock()
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_polling())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
c._send_request.mock.assert_called_once_with(
- 'GET', 'http://foo&t=123.456', timeout=30)
- c._trigger_event.mock.assert_called_once_with('disconnect',
- run_async=False)
+ 'GET', 'http://foo&t=123.456', timeout=30
+ )
+ c._trigger_event.mock.assert_called_once_with(
+ 'disconnect', run_async=False
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_read_loop_polling_bad_status(self, _time):
@@ -937,12 +1020,12 @@ class TestAsyncClient(unittest.TestCase):
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 400
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_polling())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
c._send_request.mock.assert_called_once_with(
- 'GET', 'http://foo&t=123.456', timeout=30)
+ 'GET', 'http://foo&t=123.456', timeout=30
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_read_loop_polling_bad_packet(self, _time):
@@ -955,15 +1038,14 @@ class TestAsyncClient(unittest.TestCase):
c.queue.put = AsyncMock()
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
- c._send_request.mock.return_value.read = AsyncMock(
- return_value=b'foo')
+ c._send_request.mock.return_value.read = AsyncMock(return_value=b'foo')
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_polling())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
c._send_request.mock.assert_called_once_with(
- 'GET', 'http://foo&t=123.456', timeout=65)
+ 'GET', 'http://foo&t=123.456', timeout=65
+ )
def test_read_loop_polling(self):
c = asyncio_client.AsyncClient()
@@ -975,50 +1057,72 @@ class TestAsyncClient(unittest.TestCase):
c.queue.put = AsyncMock()
c._send_request = AsyncMock()
c._send_request.mock.side_effect = [
- mock.MagicMock(status=200, read=AsyncMock(
- return_value=payload.Payload(packets=[
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP)]).encode())),
- None
+ mock.MagicMock(
+ status=200,
+ read=AsyncMock(
+ return_value=payload.Payload(
+ packets=[
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ ]
+ ).encode().encode('utf-8')
+ ),
+ ),
+ None,
]
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
c._receive_packet = AsyncMock()
_run(c._read_loop_polling())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
- self.assertEqual(c._send_request.mock.call_count, 2)
- self.assertEqual(c._receive_packet.mock.call_count, 2)
- self.assertEqual(
- c._receive_packet.mock.call_args_list[0][0][0].encode(), b'2')
- self.assertEqual(
- c._receive_packet.mock.call_args_list[1][0][0].encode(), b'6')
+ assert c._send_request.mock.call_count == 2
+ assert c._receive_packet.mock.call_count == 2
+ assert c._receive_packet.mock.call_args_list[0][0][0].encode() == '2'
+ assert c._receive_packet.mock.call_args_list[1][0][0].encode() == '6'
def test_read_loop_websocket_disconnected(self):
c = asyncio_client.AsyncClient()
c.state = 'disconnected'
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_websocket())
# should not block
+ def test_read_loop_websocket_timeout(self):
+ c = asyncio_client.AsyncClient()
+ c.ping_interval = 1
+ c.ping_timeout = 2
+ c.base_url = 'ws://foo'
+ c.state = 'connected'
+ c.queue = mock.MagicMock()
+ c.queue.put = AsyncMock()
+ c.ws = mock.MagicMock()
+ c.ws.receive = AsyncMock(side_effect=asyncio.TimeoutError())
+ c.write_loop_task = AsyncMock()()
+ _run(c._read_loop_websocket())
+ assert c.state == 'disconnected'
+ c.queue.put.mock.assert_called_once_with(None)
+
def test_read_loop_websocket_no_response(self):
c = asyncio_client.AsyncClient()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.base_url = 'ws://foo'
c.state = 'connected'
c.queue = mock.MagicMock()
c.queue.put = AsyncMock()
c.ws = mock.MagicMock()
c.ws.receive = AsyncMock(
- side_effect=aiohttp.client_exceptions.ServerDisconnectedError())
+ side_effect=aiohttp.client_exceptions.ServerDisconnectedError()
+ )
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_websocket())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
def test_read_loop_websocket_unexpected_error(self):
c = asyncio_client.AsyncClient()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.base_url = 'ws://foo'
c.state = 'connected'
c.queue = mock.MagicMock()
@@ -1026,28 +1130,30 @@ class TestAsyncClient(unittest.TestCase):
c.ws = mock.MagicMock()
c.ws.receive = AsyncMock(side_effect=ValueError)
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
_run(c._read_loop_websocket())
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.mock.assert_called_once_with(None)
def test_read_loop_websocket(self):
c = asyncio_client.AsyncClient()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.base_url = 'ws://foo'
c.state = 'connected'
c.queue = mock.MagicMock()
c.queue.put = AsyncMock()
c.ws = mock.MagicMock()
- c.ws.receive = AsyncMock(side_effect=[
- mock.MagicMock(data=packet.Packet(packet.PING).encode()),
- ValueError])
+ c.ws.receive = AsyncMock(
+ side_effect=[
+ mock.MagicMock(data=packet.Packet(packet.PING).encode()),
+ ValueError,
+ ]
+ )
c.write_loop_task = AsyncMock()()
- c.ping_loop_task = AsyncMock()()
c._receive_packet = AsyncMock()
_run(c._read_loop_websocket())
- self.assertEqual(c.state, 'disconnected')
- self.assertEqual(
- c._receive_packet.mock.call_args_list[0][0][0].encode(), b'2')
+ assert c.state == 'disconnected'
+ assert c._receive_packet.mock.call_args_list[0][0][0].encode() == '2'
c.queue.put.mock.assert_called_once_with(None)
def test_write_loop_disconnected(self):
@@ -1087,20 +1193,27 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'polling'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
c.queue.get_nowait = mock.MagicMock(side_effect=RuntimeError)
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.mock.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
def test_write_loop_polling_three_packets(self):
c = asyncio_client.AsyncClient()
@@ -1111,27 +1224,37 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'polling'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP),
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
+ c.queue.get_nowait = mock.MagicMock(
+ side_effect=[
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ RuntimeError,
+ ]
+ )
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 3)
- p = payload.Payload(packets=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP),
- ])
+ assert c.queue.task_done.call_count == 3
+ p = payload.Payload(
+ packets=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ ]
+ )
c._send_request.mock.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
def test_write_loop_polling_two_packets_done(self):
c = asyncio_client.AsyncClient()
@@ -1142,26 +1265,33 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'polling'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- packet.Packet(packet.PING),
- None
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
+ c.queue.get_nowait = mock.MagicMock(
+ side_effect=[packet.Packet(packet.PING), None]
+ )
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 200
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 3)
- p = payload.Payload(packets=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- packet.Packet(packet.PING),
- ])
+ assert c.queue.task_done.call_count == 3
+ p = payload.Payload(
+ packets=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ packet.Packet(packet.PING),
+ ]
+ )
c._send_request.mock.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'connected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'connected'
def test_write_loop_polling_bad_connection(self):
c = asyncio_client.AsyncClient()
@@ -1172,21 +1302,24 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'polling'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
+ c.queue.get_nowait = mock.MagicMock(side_effect=[RuntimeError])
c._send_request = AsyncMock(return_value=None)
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.mock.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'connected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'connected'
def test_write_loop_polling_bad_status(self):
c = asyncio_client.AsyncClient()
@@ -1197,22 +1330,25 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'polling'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
+ c.queue.get_nowait = mock.MagicMock(side_effect=[RuntimeError])
c._send_request = AsyncMock()
c._send_request.mock.return_value.status = 500
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.mock.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'disconnected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'disconnected'
def test_write_loop_websocket_one_packet(self):
c = asyncio_client.AsyncClient()
@@ -1222,18 +1358,18 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'websocket'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
+ c.queue.get_nowait = mock.MagicMock(side_effect=[RuntimeError])
c.ws = mock.MagicMock()
c.ws.send_str = AsyncMock()
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 1)
- self.assertEqual(c.ws.send_str.mock.call_count, 1)
+ assert c.queue.task_done.call_count == 1
+ assert c.ws.send_str.mock.call_count == 1
c.ws.send_str.mock.assert_called_once_with('4{"foo":"bar"}')
def test_write_loop_websocket_three_packets(self):
@@ -1244,24 +1380,27 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'websocket'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP),
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
+ c.queue.get_nowait = mock.MagicMock(
+ side_effect=[
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ RuntimeError,
+ ]
+ )
c.ws = mock.MagicMock()
c.ws.send_str = AsyncMock()
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 3)
- self.assertEqual(c.ws.send_str.mock.call_count, 3)
- self.assertEqual(c.ws.send_str.mock.call_args_list[0][0][0],
- '4{"foo":"bar"}')
- self.assertEqual(c.ws.send_str.mock.call_args_list[1][0][0], '2')
- self.assertEqual(c.ws.send_str.mock.call_args_list[2][0][0], '6')
+ assert c.queue.task_done.call_count == 3
+ assert c.ws.send_str.mock.call_count == 3
+ assert c.ws.send_str.mock.call_args_list[0][0][0] == '4{"foo":"bar"}'
+ assert c.ws.send_str.mock.call_args_list[1][0][0] == '2'
+ assert c.ws.send_str.mock.call_args_list[2][0][0] == '6'
def test_write_loop_websocket_one_packet_binary(self):
c = asyncio_client.AsyncClient()
@@ -1271,19 +1410,16 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'websocket'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, b'foo'),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[packet.Packet(packet.MESSAGE, b'foo'), RuntimeError]
+ )
+ c.queue.get_nowait = mock.MagicMock(side_effect=[RuntimeError])
c.ws = mock.MagicMock()
c.ws.send_bytes = AsyncMock()
_run(c._write_loop())
- self.assertEqual(c.queue.task_done.call_count, 1)
- self.assertEqual(c.ws.send_bytes.mock.call_count, 1)
- c.ws.send_bytes.mock.assert_called_once_with(b'\x04foo')
+ assert c.queue.task_done.call_count == 1
+ assert c.ws.send_bytes.mock.call_count == 1
+ c.ws.send_bytes.mock.assert_called_once_with(b'foo')
def test_write_loop_websocket_bad_connection(self):
c = asyncio_client.AsyncClient()
@@ -1293,15 +1429,30 @@ class TestAsyncClient(unittest.TestCase):
c.current_transport = 'websocket'
c.queue = mock.MagicMock()
c.queue.Empty = RuntimeError
- c.queue.get = AsyncMock(side_effect=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- RuntimeError
- ])
- c.queue.get_nowait = mock.MagicMock(side_effect=[
- RuntimeError
- ])
+ c.queue.get = AsyncMock(
+ side_effect=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ RuntimeError,
+ ]
+ )
+ c.queue.get_nowait = mock.MagicMock(side_effect=[RuntimeError])
c.ws = mock.MagicMock()
c.ws.send_str = AsyncMock(
- side_effect=aiohttp.client_exceptions.ServerDisconnectedError())
+ side_effect=aiohttp.client_exceptions.ServerDisconnectedError()
+ )
_run(c._write_loop())
- self.assertEqual(c.state, 'connected')
+ assert c.state == 'connected'
+
+ @mock.patch('engineio.client.original_signal_handler')
+ def test_signal_handler(self, original_handler):
+ clients = [mock.MagicMock(), mock.MagicMock()]
+ client.connected_clients = clients[:]
+ client.connected_clients[0].is_asyncio_based.return_value = False
+ client.connected_clients[1].is_asyncio_based.return_value = True
+
+ async def test():
+ asyncio_client.async_signal_handler()
+
+ asyncio.get_event_loop().run_until_complete(test())
+ clients[0].disconnect.assert_not_called()
+ clients[1].disconnect.assert_called_once_with()
diff --git a/debian/tests/asyncio/test_asyncio_server.py b/debian/tests/asyncio/test_asyncio_server.py
index 54c7254..383ae81 100644
--- a/debian/tests/asyncio/test_asyncio_server.py
+++ b/debian/tests/asyncio/test_asyncio_server.py
@@ -1,16 +1,14 @@
import asyncio
import gzip
+import io
import json
import logging
import sys
import unittest
+from unittest import mock
import zlib
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+import pytest
from engineio import asyncio_server
from engineio.async_drivers import aiohttp as async_aiohttp
@@ -39,6 +37,11 @@ def _run(coro):
class TestAsyncServer(unittest.TestCase):
@staticmethod
def get_async_mock(environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}):
+ if environ.get('QUERY_STRING'):
+ if 'EIO=' not in environ['QUERY_STRING']:
+ environ['QUERY_STRING'] = 'EIO=4&' + environ['QUERY_STRING']
+ else:
+ environ['QUERY_STRING'] = 'EIO=4'
a = mock.MagicMock()
a._async = {
'asyncio': True,
@@ -82,39 +85,36 @@ class TestAsyncServer(unittest.TestCase):
def test_is_asyncio_based(self):
s = asyncio_server.AsyncServer()
- self.assertEqual(s.is_asyncio_based(), True)
+ assert s.is_asyncio_based()
def test_async_modes(self):
s = asyncio_server.AsyncServer()
- self.assertEqual(s.async_modes(), ['aiohttp', 'sanic', 'tornado',
- 'asgi'])
+ assert s.async_modes() == ['aiohttp', 'sanic', 'tornado', 'asgi']
def test_async_mode_aiohttp(self):
s = asyncio_server.AsyncServer(async_mode='aiohttp')
- self.assertEqual(s.async_mode, 'aiohttp')
- self.assertEqual(s._async['asyncio'], True)
- self.assertEqual(s._async['create_route'], async_aiohttp.create_route)
- self.assertEqual(s._async['translate_request'],
- async_aiohttp.translate_request)
- self.assertEqual(s._async['make_response'],
- async_aiohttp.make_response)
- self.assertEqual(s._async['websocket'].__name__, 'WebSocket')
+ assert s.async_mode == 'aiohttp'
+ assert s._async['asyncio']
+ assert s._async['create_route'] == async_aiohttp.create_route
+ assert s._async['translate_request'] == async_aiohttp.translate_request
+ assert s._async['make_response'] == async_aiohttp.make_response
+ assert s._async['websocket'].__name__ == 'WebSocket'
@mock.patch('importlib.import_module')
def test_async_mode_auto_aiohttp(self, import_module):
import_module.side_effect = [self.get_async_mock()]
s = asyncio_server.AsyncServer()
- self.assertEqual(s.async_mode, 'aiohttp')
+ assert s.async_mode == 'aiohttp'
def test_async_modes_wsgi(self):
- self.assertRaises(ValueError, asyncio_server.AsyncServer,
- async_mode='eventlet')
- self.assertRaises(ValueError, asyncio_server.AsyncServer,
- async_mode='gevent')
- self.assertRaises(ValueError, asyncio_server.AsyncServer,
- async_mode='gevent_uwsgi')
- self.assertRaises(ValueError, asyncio_server.AsyncServer,
- async_mode='threading')
+ with pytest.raises(ValueError):
+ asyncio_server.AsyncServer(async_mode='eventlet')
+ with pytest.raises(ValueError):
+ asyncio_server.AsyncServer(async_mode='gevent')
+ with pytest.raises(ValueError):
+ asyncio_server.AsyncServer(async_mode='gevent_uwsgi')
+ with pytest.raises(ValueError):
+ asyncio_server.AsyncServer(async_mode='threading')
@mock.patch('importlib.import_module')
def test_attach(self, import_module):
@@ -138,7 +138,7 @@ class TestAsyncServer(unittest.TestCase):
async with s.session('foo') as session:
await s.sleep(0)
session['username'] = 'bar'
- self.assertEqual(await s.get_session('foo'), {'username': 'bar'})
+ assert await s.get_session('foo') == {'username': 'bar'}
_run(_func())
@@ -146,50 +146,53 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
_run(s.disconnect('foo'))
- self.assertEqual(mock_socket.close.mock.call_count, 1)
+ assert mock_socket.close.mock.call_count == 1
mock_socket.close.mock.assert_called_once_with()
- self.assertNotIn('foo', s.sockets)
+ assert 'foo' not in s.sockets
def test_disconnect_all(self):
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_foo = self._get_mock_socket()
s.sockets['bar'] = mock_bar = self._get_mock_socket()
_run(s.disconnect())
- self.assertEqual(mock_foo.close.mock.call_count, 1)
- self.assertEqual(mock_bar.close.mock.call_count, 1)
+ assert mock_foo.close.mock.call_count == 1
+ assert mock_bar.close.mock.call_count == 1
mock_foo.close.mock.assert_called_once_with()
mock_bar.close.mock.assert_called_once_with()
- self.assertNotIn('foo', s.sockets)
- self.assertNotIn('bar', s.sockets)
+ assert 'foo' not in s.sockets
+ assert 'bar' not in s.sockets
@mock.patch('importlib.import_module')
def test_jsonp_not_supported(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'j=abc'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=abc'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
response = _run(s.handle_request('request'))
- self.assertEqual(response, 'response')
+ assert response == 'response'
a._async['translate_request'].assert_called_once_with('request')
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@mock.patch('importlib.import_module')
def test_jsonp_index(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'j=233'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=233'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
response = _run(s.handle_request('request'))
- self.assertEqual(response, 'response')
+ assert response == 'response'
a._async['translate_request'].assert_called_once_with('request')
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertTrue(a._async['make_response'].call_args[0][2].startswith(
- b'___eio[233]("'))
- self.assertTrue(a._async['make_response'].call_args[0][2].endswith(
- b'");'))
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
+ assert (
+ a._async['make_response']
+ .call_args[0][2]
+ .startswith(b'___eio[233]("')
+ )
+ assert a._async['make_response'].call_args[0][2].endswith(b'");')
@mock.patch('importlib.import_module')
def test_connect(self, import_module):
@@ -197,42 +200,47 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 1)
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- a._async['make_response'].call_args[0][1])
+ assert len(s.sockets) == 1
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
+ assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[
+ 'make_response'
+ ].call_args[0][1]
packets = payload.Payload(
- encoded_payload=a._async['make_response'].call_args[0][2]).packets
- self.assertEqual(len(packets), 1)
- self.assertEqual(packets[0].packet_type, packet.OPEN)
- self.assertIn('upgrades', packets[0].data)
- self.assertEqual(packets[0].data['upgrades'], ['websocket'])
- self.assertIn('sid', packets[0].data)
+ encoded_payload=a._async['make_response'].call_args[0][2].decode(
+ 'utf-8')).packets
+ assert len(packets) == 1
+ assert packets[0].packet_type == packet.OPEN
+ assert 'upgrades' in packets[0].data
+ assert packets[0].data['upgrades'] == ['websocket']
+ assert 'sid' in packets[0].data
@mock.patch('importlib.import_module')
def test_connect_async_request_response_handlers(self, import_module):
a = self.get_async_mock()
a._async['translate_request'] = AsyncMock(
- return_value=a._async['translate_request'].return_value)
+ return_value=a._async['translate_request'].return_value
+ )
a._async['make_response'] = AsyncMock(
- return_value=a._async['make_response'].return_value)
+ return_value=a._async['make_response'].return_value
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 1)
- self.assertEqual(a._async['make_response'].mock.call_count, 1)
- self.assertEqual(a._async['make_response'].mock.call_args[0][0],
- '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- a._async['make_response'].mock.call_args[0][1])
- packets = payload.Payload(encoded_payload=a._async[
- 'make_response'].mock.call_args[0][2]).packets
- self.assertEqual(len(packets), 1)
- self.assertEqual(packets[0].packet_type, packet.OPEN)
- self.assertIn('upgrades', packets[0].data)
- self.assertEqual(packets[0].data['upgrades'], ['websocket'])
- self.assertIn('sid', packets[0].data)
+ assert len(s.sockets) == 1
+ assert a._async['make_response'].mock.call_count == 1
+ assert a._async['make_response'].mock.call_args[0][0] == '200 OK'
+ assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[
+ 'make_response'
+ ].mock.call_args[0][1]
+ packets = payload.Payload(
+ encoded_payload=a._async['make_response'].mock.call_args[0][
+ 2].decode('utf-8')).packets
+ assert len(packets) == 1
+ assert packets[0].packet_type == packet.OPEN
+ assert 'upgrades' in packets[0].data
+ assert packets[0].data['upgrades'] == ['websocket']
+ assert 'sid' in packets[0].data
@mock.patch('importlib.import_module')
def test_connect_no_upgrades(self, import_module):
@@ -241,84 +249,22 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer(allow_upgrades=False)
_run(s.handle_request('request'))
packets = payload.Payload(
- encoded_payload=a._async['make_response'].call_args[0][2]).packets
- self.assertEqual(packets[0].data['upgrades'], [])
-
- @mock.patch('importlib.import_module')
- def test_connect_b64_with_1(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'b64=1'})
- import_module.side_effect = [a]
- s = asyncio_server.AsyncServer(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'text/plain; charset=UTF-8'),
- a._async['make_response'].call_args[0][1])
- _run(s.send('1', b'\x00\x01\x02', binary=True))
- a._async['translate_request'].return_value = {
- 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=1'}
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][2],
- b'6:b4AAEC')
+ encoded_payload=a._async['make_response'].call_args[0][2].decode(
+ 'utf-8')).packets
+ assert packets[0].data['upgrades'] == []
@mock.patch('importlib.import_module')
- def test_connect_b64_with_true(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'b64=true'})
+ def test_connect_bad_eio_version(self, import_module):
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=1'}
+ )
import_module.side_effect = [a]
- s = asyncio_server.AsyncServer(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'text/plain; charset=UTF-8'),
- a._async['make_response'].call_args[0][1])
- _run(s.send('1', b'\x00\x01\x02', binary=True))
- a._async['translate_request'].return_value = {
- 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=true'}
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][2],
- b'6:b4AAEC')
-
- @mock.patch('importlib.import_module')
- def test_connect_b64_with_0(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'b64=0'})
- import_module.side_effect = [a]
- s = asyncio_server.AsyncServer(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- a._async['make_response'].call_args[0][1])
- _run(s.send('1', b'\x00\x01\x02', binary=True))
- a._async['translate_request'].return_value = {
- 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=0'}
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][2],
- b'\x01\x04\xff\x04\x00\x01\x02')
-
- @mock.patch('importlib.import_module')
- def test_connect_b64_with_false(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'b64=false'})
- import_module.side_effect = [a]
- s = asyncio_server.AsyncServer(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- _run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- a._async['make_response'].call_args[0][1])
- _run(s.send('1', b'\x00\x01\x02', binary=True))
- a._async['translate_request'].return_value = {
- 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=false'}
+ s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][2],
- b'\x01\x04\xff\x04\x00\x01\x02')
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
+ assert b'unsupported version' in \
+ a._async['make_response'].call_args[0][2]
@mock.patch('importlib.import_module')
def test_connect_custom_ping_times(self, import_module):
@@ -327,9 +273,10 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer(ping_timeout=123, ping_interval=456)
_run(s.handle_request('request'))
packets = payload.Payload(
- encoded_payload=a._async['make_response'].call_args[0][2]).packets
- self.assertEqual(packets[0].data['pingTimeout'], 123000)
- self.assertEqual(packets[0].data['pingInterval'], 456000)
+ encoded_payload=a._async['make_response'].call_args[0][2].decode(
+ 'utf-8')).packets
+ assert packets[0].data['pingTimeout'] == 123000
+ assert packets[0].data['pingInterval'] == 456000
@mock.patch('engineio.asyncio_socket.AsyncSocket')
@mock.patch('importlib.import_module')
@@ -340,36 +287,69 @@ class TestAsyncServer(unittest.TestCase):
AsyncSocket.return_value.poll.side_effect = [exceptions.QueueEmpty]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@mock.patch('engineio.asyncio_socket.AsyncSocket')
@mock.patch('importlib.import_module')
def test_connect_transport_websocket(self, import_module, AsyncSocket):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'transport=websocket'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'transport=websocket',
+ 'HTTP_UPGRADE': 'websocket',
+ }
+ )
import_module.side_effect = [a]
AsyncSocket.return_value = self._get_mock_socket()
s = asyncio_server.AsyncServer()
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
# force socket to stay open, so that we can check it later
AsyncSocket().closed = False
_run(s.handle_request('request'))
- self.assertEqual(
- s.sockets['123'].send.mock.call_args[0][0].packet_type,
- packet.OPEN)
+ assert (
+ s.sockets['123'].send.mock.call_args[0][0].packet_type
+ == packet.OPEN
+ )
@mock.patch('engineio.asyncio_socket.AsyncSocket')
@mock.patch('importlib.import_module')
- def test_connect_transport_websocket_closed(self, import_module,
- AsyncSocket):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'transport=websocket'})
+ def test_http_upgrade_case_insensitive(self, import_module, AsyncSocket):
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'transport=websocket',
+ 'HTTP_UPGRADE': 'WebSocket',
+ }
+ )
import_module.side_effect = [a]
AsyncSocket.return_value = self._get_mock_socket()
s = asyncio_server.AsyncServer()
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
+ # force socket to stay open, so that we can check it later
+ AsyncSocket().closed = False
+ _run(s.handle_request('request'))
+ assert (
+ s.sockets['123'].send.mock.call_args[0][0].packet_type
+ == packet.OPEN
+ )
+
+ @mock.patch('engineio.asyncio_socket.AsyncSocket')
+ @mock.patch('importlib.import_module')
+ def test_connect_transport_websocket_closed(
+ self, import_module, AsyncSocket
+ ):
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'transport=websocket',
+ 'HTTP_UPGRADE': 'websocket'
+ }
+ )
+ import_module.side_effect = [a]
+ AsyncSocket.return_value = self._get_mock_socket()
+ s = asyncio_server.AsyncServer()
+ s.generate_id = mock.MagicMock(return_value='123')
# this mock handler just closes the socket, as it would happen on a
# real websocket exchange
@@ -378,18 +358,29 @@ class TestAsyncServer(unittest.TestCase):
AsyncSocket().handle_get_request = mock_handle
_run(s.handle_request('request'))
- self.assertNotIn('123', s.sockets) # socket should close on its own
+ assert '123' not in s.sockets # socket should close on its own
@mock.patch('importlib.import_module')
def test_connect_transport_invalid(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'transport=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_count, 1)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
+
+ @mock.patch('importlib.import_module')
+ def test_connect_transport_websocket_without_upgrade(self, import_module):
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket'}
+ )
+ import_module.side_effect = [a]
+ s = asyncio_server.AsyncServer()
+ _run(s.handle_request('request'))
+ assert a._async['make_response'].call_count == 1
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@mock.patch('importlib.import_module')
def test_connect_cors_headers(self, import_module):
@@ -397,105 +388,111 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
@mock.patch('importlib.import_module')
def test_connect_cors_allowed_origin(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'b'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=['a', 'b'])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'b'), headers)
+ assert ('Access-Control-Allow-Origin', 'b') in headers
@mock.patch('importlib.import_module')
def test_connect_cors_not_allowed_origin(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'c'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=['a', 'b'])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
headers = a._async['make_response'].call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Origin', 'c'), headers)
- self.assertNotIn(('Access-Control-Allow-Origin', '*'), headers)
+ assert ('Access-Control-Allow-Origin', 'c') not in headers
+ assert ('Access-Control-Allow-Origin', '*') not in headers
@mock.patch('importlib.import_module')
- def test_connect_cors_not_allowed_origin_async_response(self,
- import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'c'})
+ def test_connect_cors_not_allowed_origin_async_response(
+ self, import_module
+ ):
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'}
+ )
a._async['make_response'] = AsyncMock(
- return_value=a._async['make_response'].return_value)
+ return_value=a._async['make_response'].return_value
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=['a', 'b'])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].mock.call_args[0][0],
- '400 BAD REQUEST')
+ assert (
+ a._async['make_response'].mock.call_args[0][0] == '400 BAD REQUEST'
+ )
headers = a._async['make_response'].mock.call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Origin', 'c'), headers)
- self.assertNotIn(('Access-Control-Allow-Origin', '*'), headers)
+ assert ('Access-Control-Allow-Origin', 'c') not in headers
+ assert ('Access-Control-Allow-Origin', '*') not in headers
@mock.patch('importlib.import_module')
def test_connect_cors_all_origins(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins='*')
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'foo'), headers)
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Origin', 'foo') in headers
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
@mock.patch('importlib.import_module')
def test_connect_cors_one_origin(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'a'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'a'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins='a')
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'a'), headers)
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Origin', 'a') in headers
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
@mock.patch('importlib.import_module')
def test_connect_cors_one_origin_not_allowed(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'b'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins='a')
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
headers = a._async['make_response'].call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Origin', 'b'), headers)
- self.assertNotIn(('Access-Control-Allow-Origin', '*'), headers)
+ assert ('Access-Control-Allow-Origin', 'b') not in headers
+ assert ('Access-Control-Allow-Origin', '*') not in headers
@mock.patch('importlib.import_module')
def test_connect_cors_headers_default_origin(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'wsgi.url_scheme': 'http',
- 'HTTP_HOST': 'foo',
- 'HTTP_ORIGIN': 'http://foo'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': '',
+ 'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'foo',
+ 'HTTP_ORIGIN': 'http://foo',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'http://foo'),
- headers)
+ assert ('Access-Control-Allow-Origin', 'http://foo') in headers
@mock.patch('importlib.import_module')
def test_connect_cors_no_credentials(self, import_module):
@@ -503,36 +500,41 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_credentials=False)
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Credentials', 'true') not in headers
@mock.patch('importlib.import_module')
def test_connect_cors_options(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'OPTIONS',
- 'QUERY_STRING': ''})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': ''}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_credentials=False)
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Access-Control-Allow-Methods',
- 'OPTIONS, GET, POST'), headers)
+ assert (
+ 'Access-Control-Allow-Methods',
+ 'OPTIONS, GET, POST',
+ ) in headers
@mock.patch('importlib.import_module')
def test_connect_cors_disabled(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'http://foo'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': '',
+ 'HTTP_ORIGIN': 'http://foo',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=[])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
for header in headers:
- self.assertFalse(header[0].startswith('Access-Control-'))
+ assert not header[0].startswith('Access-Control-')
@mock.patch('importlib.import_module')
def test_connect_cors_default_no_origin(self, import_module):
@@ -540,11 +542,10 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=[])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
@mock.patch('importlib.import_module')
def test_connect_cors_all_no_origin(self, import_module):
@@ -552,11 +553,10 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins='*')
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
@mock.patch('importlib.import_module')
def test_connect_cors_disabled_no_origin(self, import_module):
@@ -564,41 +564,58 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cors_allowed_origins=[])
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
headers = a._async['make_response'].call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
@mock.patch('importlib.import_module')
def test_connect_event(self, import_module):
a = self.get_async_mock()
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
def mock_connect(sid, environ):
return True
s.on('connect', handler=mock_connect)
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 1)
+ assert len(s.sockets) == 1
@mock.patch('importlib.import_module')
def test_connect_event_rejects(self, import_module):
a = self.get_async_mock()
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
def mock_connect(sid, environ):
return False
s.on('connect')(mock_connect)
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 0)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '401 UNAUTHORIZED')
+ assert len(s.sockets) == 0
+ assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED'
+ assert a._async['make_response'].call_args[0][2] == b'"Unauthorized"'
+
+ @mock.patch('importlib.import_module')
+ def test_connect_event_rejects_with_message(self, import_module):
+ a = self.get_async_mock()
+ import_module.side_effect = [a]
+ s = asyncio_server.AsyncServer()
+ s.generate_id = mock.MagicMock(return_value='123')
+
+ def mock_connect(sid, environ):
+ return {'not': 'allowed'}
+
+ s.on('connect')(mock_connect)
+ _run(s.handle_request('request'))
+ assert len(s.sockets) == 0
+ assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED'
+ assert (
+ a._async['make_response'].call_args[0][2] == b'{"not": "allowed"}'
+ )
@mock.patch('importlib.import_module')
def test_method_not_found(self, import_module):
@@ -606,31 +623,32 @@ class TestAsyncServer(unittest.TestCase):
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 0)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '405 METHOD NOT FOUND')
+ assert len(s.sockets) == 0
+ assert (
+ a._async['make_response'].call_args[0][0] == '405 METHOD NOT FOUND'
+ )
@mock.patch('importlib.import_module')
def test_get_request_with_bad_sid(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 0)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert len(s.sockets) == 0
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@mock.patch('importlib.import_module')
def test_post_request_with_bad_sid(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'POST',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
_run(s.handle_request('request'))
- self.assertEqual(len(s.sockets), 0)
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert len(s.sockets) == 0
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@mock.patch('importlib.import_module')
def test_send(self, import_module):
@@ -639,10 +657,11 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
_run(s.send('foo', 'hello'))
- self.assertEqual(mock_socket.send.mock.call_count, 1)
- self.assertEqual(mock_socket.send.mock.call_args[0][0].packet_type,
- packet.MESSAGE)
- self.assertEqual(mock_socket.send.mock.call_args[0][0].data, 'hello')
+ assert mock_socket.send.mock.call_count == 1
+ assert (
+ mock_socket.send.mock.call_args[0][0].packet_type == packet.MESSAGE
+ )
+ assert mock_socket.send.mock.call_args[0][0].data == 'hello'
@mock.patch('importlib.import_module')
def test_send_unknown_socket(self, import_module):
@@ -654,35 +673,41 @@ class TestAsyncServer(unittest.TestCase):
@mock.patch('importlib.import_module')
def test_get_request(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
packets = payload.Payload(
- encoded_payload=a._async['make_response'].call_args[0][2]).packets
- self.assertEqual(len(packets), 1)
- self.assertEqual(packets[0].packet_type, packet.MESSAGE)
+ encoded_payload=a._async['make_response'].call_args[0][2].decode(
+ 'utf-8')
+ ).packets
+ assert len(packets) == 1
+ assert packets[0].packet_type == packet.MESSAGE
@mock.patch('importlib.import_module')
def test_get_request_custom_response(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
mock_socket.handle_get_request.mock.return_value = 'resp'
r = _run(s.handle_request('request'))
- self.assertEqual(r, 'resp')
+ assert r == 'resp'
@mock.patch('importlib.import_module')
def test_get_request_closes_socket(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
@@ -693,13 +718,14 @@ class TestAsyncServer(unittest.TestCase):
mock_socket.handle_get_request = mock_get_request
r = _run(s.handle_request('request'))
- self.assertEqual(r, 'resp')
- self.assertNotIn('foo', s.sockets)
+ assert r == 'resp'
+ assert 'foo' not in s.sockets
@mock.patch('importlib.import_module')
def test_get_request_error(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
@@ -709,24 +735,25 @@ class TestAsyncServer(unittest.TestCase):
mock_socket.handle_get_request = mock_get_request
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
- self.assertEqual(len(s.sockets), 0)
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
+ assert len(s.sockets) == 0
@mock.patch('importlib.import_module')
def test_post_request(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'POST',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = self._get_mock_socket()
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0], '200 OK')
+ assert a._async['make_response'].call_args[0][0] == '200 OK'
@mock.patch('importlib.import_module')
def test_post_request_error(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'POST',
- 'QUERY_STRING': 'sid=foo'})
+ a = self.get_async_mock(
+ {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer()
s.sockets['foo'] = mock_socket = self._get_mock_socket()
@@ -736,148 +763,198 @@ class TestAsyncServer(unittest.TestCase):
mock_socket.handle_post_request = mock_post_request
_run(s.handle_request('request'))
- self.assertEqual(a._async['make_response'].call_args[0][0],
- '400 BAD REQUEST')
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
@staticmethod
def _gzip_decompress(b):
- bytesio = six.BytesIO(b)
+ bytesio = io.BytesIO(b)
with gzip.GzipFile(fileobj=bytesio, mode='r') as gz:
return gz.read()
@mock.patch('importlib.import_module')
def test_gzip_compression(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip,deflate'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(compression_threshold=0)
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Content-Encoding', 'gzip'), headers)
+ assert ('Content-Encoding', 'gzip') in headers
self._gzip_decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_deflate_compression(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(compression_threshold=0)
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Content-Encoding', 'deflate'), headers)
+ assert ('Content-Encoding', 'deflate') in headers
zlib.decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_gzip_compression_threshold(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(compression_threshold=1000)
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
for header, value in headers:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress,
- a._async['make_response'].call_args[0][2])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_compression_disabled(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip',
+ }
+ )
import_module.side_effect = [a]
- s = asyncio_server.AsyncServer(http_compression=False,
- compression_threshold=0)
+ s = asyncio_server.AsyncServer(
+ http_compression=False, compression_threshold=0
+ )
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
for header, value in headers:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress,
- a._async['make_response'].call_args[0][2])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_compression_unknown(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'rar'})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'rar',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(compression_threshold=0)
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
for header, value in headers:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress,
- a._async['make_response'].call_args[0][2])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_compression_no_encoding(self, import_module):
- a = self.get_async_mock({'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': ''})
+ a = self.get_async_mock(
+ {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_ACCEPT_ENCODING': '',
+ }
+ )
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(compression_threshold=0)
s.sockets['foo'] = mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request.mock.return_value = \
- [packet.Packet(packet.MESSAGE, data='hello')]
+ mock_socket.handle_get_request.mock.return_value = [
+ packet.Packet(packet.MESSAGE, data='hello')
+ ]
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
for header, value in headers:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress,
- a._async['make_response'].call_args[0][2])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(a._async['make_response'].call_args[0][2])
@mock.patch('importlib.import_module')
def test_cookie(self, import_module):
a = self.get_async_mock()
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cookie='sid')
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
+ _run(s.handle_request('request'))
+ headers = a._async['make_response'].call_args[0][1]
+ assert ('Set-Cookie', 'sid=123; path=/; SameSite=Lax') in headers
+
+ @mock.patch('importlib.import_module')
+ def test_cookie_dict(self, import_module):
+ def get_path():
+ return '/a'
+
+ a = self.get_async_mock()
+ import_module.side_effect = [a]
+ s = asyncio_server.AsyncServer(cookie={
+ 'name': 'test',
+ 'path': get_path,
+ 'SameSite': 'None',
+ 'Secure': True,
+ 'HttpOnly': True
+ })
+ s.generate_id = mock.MagicMock(return_value='123')
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
- self.assertIn(('Set-Cookie', 'sid=123'), headers)
+ assert ('Set-Cookie', 'test=123; path=/a; SameSite=None; Secure; '
+ 'HttpOnly') in headers
@mock.patch('importlib.import_module')
def test_no_cookie(self, import_module):
a = self.get_async_mock()
import_module.side_effect = [a]
s = asyncio_server.AsyncServer(cookie=None)
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
_run(s.handle_request('request'))
headers = a._async['make_response'].call_args[0][1]
for header, value in headers:
- self.assertNotEqual(header, 'Set-Cookie')
+ assert header != 'Set-Cookie'
def test_logger(self):
s = asyncio_server.AsyncServer(logger=False)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.ERROR)
+ assert s.logger.getEffectiveLevel() == logging.ERROR
s.logger.setLevel(logging.NOTSET)
s = asyncio_server.AsyncServer(logger=True)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.INFO)
+ assert s.logger.getEffectiveLevel() == logging.INFO
s.logger.setLevel(logging.WARNING)
s = asyncio_server.AsyncServer(logger=True)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.WARNING)
+ assert s.logger.getEffectiveLevel() == logging.WARNING
s.logger.setLevel(logging.NOTSET)
my_logger = logging.Logger('foo')
s = asyncio_server.AsyncServer(logger=my_logger)
- self.assertEqual(s.logger, my_logger)
+ assert s.logger == my_logger
def test_custom_json(self):
# Warning: this test cannot run in parallel with other tests, as it
@@ -894,9 +971,9 @@ class TestAsyncServer(unittest.TestCase):
asyncio_server.AsyncServer(json=CustomJSON)
pkt = packet.Packet(packet.MESSAGE, data={'foo': 'bar'})
- self.assertEqual(pkt.encode(), b'4*** encoded ***')
+ assert pkt.encode() == '4*** encoded ***'
pkt2 = packet.Packet(encoded_packet=pkt.encode())
- self.assertEqual(pkt2.data, '+++ decoded +++')
+ assert pkt2.data == '+++ decoded +++'
# restore the default JSON module
packet.Packet.json = json
@@ -909,9 +986,10 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.start_background_task(foo, 'bar')
- pending = asyncio.Task.all_tasks()
+ pending = asyncio.all_tasks(loop=asyncio.get_event_loop()) \
+ if hasattr(asyncio, 'all_tasks') else asyncio.Task.all_tasks()
asyncio.get_event_loop().run_until_complete(asyncio.wait(pending))
- self.assertEqual(r, ['bar'])
+ assert r == ['bar']
def test_sleep(self):
s = asyncio_server.AsyncServer()
@@ -927,7 +1005,7 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('message', handler=foo_handler)
_run(s._trigger_event('message', 'bar'))
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_coroutine(self):
result = []
@@ -939,7 +1017,7 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('message', handler=foo_handler)
_run(s._trigger_event('message', 'bar'))
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_function_error(self):
def connect_handler(arg):
@@ -951,8 +1029,8 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('connect', handler=connect_handler)
s.on('message', handler=foo_handler)
- self.assertFalse(_run(s._trigger_event('connect', '123')))
- self.assertIsNone(_run(s._trigger_event('message', 'bar')))
+ assert not _run(s._trigger_event('connect', '123'))
+ assert _run(s._trigger_event('message', 'bar')) is None
def test_trigger_event_coroutine_error(self):
async def connect_handler(arg):
@@ -964,8 +1042,8 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('connect', handler=connect_handler)
s.on('message', handler=foo_handler)
- self.assertFalse(_run(s._trigger_event('connect', '123')))
- self.assertIsNone(_run(s._trigger_event('message', 'bar')))
+ assert not _run(s._trigger_event('connect', '123'))
+ assert _run(s._trigger_event('message', 'bar')) is None
def test_trigger_event_function_async(self):
result = []
@@ -978,7 +1056,7 @@ class TestAsyncServer(unittest.TestCase):
s.on('message', handler=foo_handler)
fut = _run(s._trigger_event('message', 'bar', run_async=True))
asyncio.get_event_loop().run_until_complete(fut)
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_coroutine_async(self):
result = []
@@ -991,7 +1069,7 @@ class TestAsyncServer(unittest.TestCase):
s.on('message', handler=foo_handler)
fut = _run(s._trigger_event('message', 'bar', run_async=True))
asyncio.get_event_loop().run_until_complete(fut)
- self.assertEqual(result, ['ok', 'bar'])
+ assert result == ['ok', 'bar']
def test_trigger_event_function_async_error(self):
result = []
@@ -1003,10 +1081,9 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('message', handler=foo_handler)
fut = _run(s._trigger_event('message', 'bar', run_async=True))
- self.assertRaises(
- ZeroDivisionError, asyncio.get_event_loop().run_until_complete,
- fut)
- self.assertEqual(result, ['bar'])
+ with pytest.raises(ZeroDivisionError):
+ asyncio.get_event_loop().run_until_complete(fut)
+ assert result == ['bar']
def test_trigger_event_coroutine_async_error(self):
result = []
@@ -1018,23 +1095,23 @@ class TestAsyncServer(unittest.TestCase):
s = asyncio_server.AsyncServer()
s.on('message', handler=foo_handler)
fut = _run(s._trigger_event('message', 'bar', run_async=True))
- self.assertRaises(
- ZeroDivisionError, asyncio.get_event_loop().run_until_complete,
- fut)
- self.assertEqual(result, ['bar'])
+ with pytest.raises(ZeroDivisionError):
+ asyncio.get_event_loop().run_until_complete(fut)
+ assert result == ['bar']
def test_create_queue(self):
s = asyncio_server.AsyncServer()
q = s.create_queue()
empty = s.get_queue_empty_exception()
- self.assertRaises(empty, q.get_nowait)
+ with pytest.raises(empty):
+ q.get_nowait()
def test_create_event(self):
s = asyncio_server.AsyncServer()
e = s.create_event()
- self.assertFalse(e.is_set())
+ assert not e.is_set()
e.set()
- self.assertTrue(e.is_set())
+ assert e.is_set()
@mock.patch('importlib.import_module')
def test_service_task_started(self, import_module):
diff --git a/debian/tests/asyncio/test_asyncio_socket.py b/debian/tests/asyncio/test_asyncio_socket.py
index 91013ab..147c1b7 100644
--- a/debian/tests/asyncio/test_asyncio_socket.py
+++ b/debian/tests/asyncio/test_asyncio_socket.py
@@ -2,12 +2,9 @@ import asyncio
import sys
import time
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+import pytest
from engineio import asyncio_socket
from engineio import exceptions
@@ -45,39 +42,46 @@ class TestSocket(unittest.TestCase):
mock_server.ping_interval = 0.2
mock_server.ping_interval_grace_period = 0.001
mock_server.async_handlers = False
- mock_server._async = {'asyncio': True,
- 'create_route': mock.MagicMock(),
- 'translate_request': mock.MagicMock(),
- 'make_response': mock.MagicMock(),
- 'websocket': 'w'}
+ mock_server._async = {
+ 'asyncio': True,
+ 'create_route': mock.MagicMock(),
+ 'translate_request': mock.MagicMock(),
+ 'make_response': mock.MagicMock(),
+ 'websocket': 'w',
+ }
mock_server._async['translate_request'].return_value = 'request'
mock_server._async['make_response'].return_value = 'response'
mock_server._trigger_event = AsyncMock()
+ def bg_task(target, *args, **kwargs):
+ return asyncio.ensure_future(target(*args, **kwargs))
+
def create_queue(*args, **kwargs):
queue = asyncio.Queue(*args, **kwargs)
queue.Empty = asyncio.QueueEmpty
return queue
+ mock_server.start_background_task = bg_task
mock_server.create_queue = create_queue
return mock_server
def test_create(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
- self.assertEqual(s.server, mock_server)
- self.assertEqual(s.sid, 'sid')
- self.assertFalse(s.upgraded)
- self.assertFalse(s.closed)
- self.assertTrue(hasattr(s.queue, 'get'))
- self.assertTrue(hasattr(s.queue, 'put'))
- self.assertTrue(hasattr(s.queue, 'task_done'))
- self.assertTrue(hasattr(s.queue, 'join'))
+ assert s.server == mock_server
+ assert s.sid == 'sid'
+ assert not s.upgraded
+ assert not s.closed
+ assert hasattr(s.queue, 'get')
+ assert hasattr(s.queue, 'put')
+ assert hasattr(s.queue, 'task_done')
+ assert hasattr(s.queue, 'join')
def test_empty_poll(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
- self.assertRaises(exceptions.QueueEmpty, _run, s.poll())
+ with pytest.raises(exceptions.QueueEmpty):
+ _run(s.poll())
def test_poll(self):
mock_server = self._get_mock_server()
@@ -86,28 +90,66 @@ class TestSocket(unittest.TestCase):
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
_run(s.send(pkt1))
_run(s.send(pkt2))
- self.assertEqual(_run(s.poll()), [pkt1, pkt2])
+ assert _run(s.poll()) == [pkt1, pkt2]
def test_poll_none(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
_run(s.queue.put(None))
- self.assertEqual(_run(s.poll()), [])
+ assert _run(s.poll()) == []
- def test_ping_pong(self):
+ def test_poll_none_after_packet(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
- _run(s.receive(packet.Packet(packet.PING, data='abc')))
- r = _run(s.poll())
- self.assertEqual(len(r), 1)
- self.assertTrue(r[0].encode(), b'3abc')
+ pkt = packet.Packet(packet.MESSAGE, data='hello')
+ _run(s.send(pkt))
+ _run(s.queue.put(None))
+ assert _run(s.poll()) == [pkt]
+ assert _run(s.poll()) == []
+
+ def test_schedule_ping(self):
+ mock_server = self._get_mock_server()
+ mock_server.ping_interval = 0.01
+ s = asyncio_socket.AsyncSocket(mock_server, 'sid')
+ s.send = AsyncMock()
+
+ async def schedule_ping():
+ s.schedule_ping()
+ await asyncio.sleep(0.05)
+
+ _run(schedule_ping())
+ assert s.last_ping is not None
+ assert s.send.mock.call_args_list[0][0][0].encode() == '2'
+
+ def test_schedule_ping_closed_socket(self):
+ mock_server = self._get_mock_server()
+ mock_server.ping_interval = 0.01
+ s = asyncio_socket.AsyncSocket(mock_server, 'sid')
+ s.send = AsyncMock()
+ s.closed = True
+
+ async def schedule_ping():
+ s.schedule_ping()
+ await asyncio.sleep(0.05)
+
+ _run(schedule_ping())
+ assert s.last_ping is None
+ s.send.mock.assert_not_called()
+
+ def test_pong(self):
+ mock_server = self._get_mock_server()
+ s = asyncio_socket.AsyncSocket(mock_server, 'sid')
+ s.schedule_ping = mock.MagicMock()
+ _run(s.receive(packet.Packet(packet.PONG, data='abc')))
+ s.schedule_ping.assert_called_once_with()
def test_message_sync_handler(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
_run(s.receive(packet.Packet(packet.MESSAGE, data='foo')))
mock_server._trigger_event.mock.assert_called_once_with(
- 'message', 'sid', 'foo', run_async=False)
+ 'message', 'sid', 'foo', run_async=False
+ )
def test_message_async_handler(self):
mock_server = self._get_mock_server()
@@ -115,13 +157,14 @@ class TestSocket(unittest.TestCase):
mock_server.async_handlers = True
_run(s.receive(packet.Packet(packet.MESSAGE, data='foo')))
mock_server._trigger_event.mock.assert_called_once_with(
- 'message', 'sid', 'foo', run_async=True)
+ 'message', 'sid', 'foo', run_async=True
+ )
def test_invalid_packet(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
- self.assertRaises(exceptions.UnknownPacketError, _run,
- s.receive(packet.Packet(packet.OPEN)))
+ with pytest.raises(exceptions.UnknownPacketError):
+ _run(s.receive(packet.Packet(packet.OPEN)))
def test_timeout(self):
mock_server = self._get_mock_server()
@@ -142,50 +185,59 @@ class TestSocket(unittest.TestCase):
_run(s.send(pkt2))
environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
packets = _run(s.handle_get_request(environ))
- self.assertEqual(packets, [pkt1, pkt2])
+ assert packets == [pkt1, pkt2]
def test_polling_read_error(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'foo')
environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
- self.assertRaises(exceptions.QueueEmpty, _run,
- s.handle_get_request(environ))
+ with pytest.raises(exceptions.QueueEmpty):
+ _run(s.handle_get_request(environ))
def test_polling_write(self):
mock_server = self._get_mock_server()
mock_server.max_http_buffer_size = 1000
pkt1 = packet.Packet(packet.MESSAGE, data='hello')
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
- p = payload.Payload(packets=[pkt1, pkt2]).encode()
+ p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8')
s = asyncio_socket.AsyncSocket(mock_server, 'foo')
s.receive = AsyncMock()
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo',
- 'CONTENT_LENGTH': len(p),
- 'wsgi.input': self._get_read_mock_coro(p)}
+ environ = {
+ 'REQUEST_METHOD': 'POST',
+ 'QUERY_STRING': 'sid=foo',
+ 'CONTENT_LENGTH': len(p),
+ 'wsgi.input': self._get_read_mock_coro(p),
+ }
_run(s.handle_post_request(environ))
- self.assertEqual(s.receive.mock.call_count, 2)
+ assert s.receive.mock.call_count == 2
def test_polling_write_too_large(self):
mock_server = self._get_mock_server()
pkt1 = packet.Packet(packet.MESSAGE, data='hello')
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
- p = payload.Payload(packets=[pkt1, pkt2]).encode()
+ p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8')
mock_server.max_http_buffer_size = len(p) - 1
s = asyncio_socket.AsyncSocket(mock_server, 'foo')
s.receive = AsyncMock()
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo',
- 'CONTENT_LENGTH': len(p),
- 'wsgi.input': self._get_read_mock_coro(p)}
- self.assertRaises(exceptions.ContentTooLongError, _run,
- s.handle_post_request(environ))
+ environ = {
+ 'REQUEST_METHOD': 'POST',
+ 'QUERY_STRING': 'sid=foo',
+ 'CONTENT_LENGTH': len(p),
+ 'wsgi.input': self._get_read_mock_coro(p),
+ }
+ with pytest.raises(exceptions.ContentTooLongError):
+ _run(s.handle_post_request(environ))
def test_upgrade_handshake(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'foo')
s._upgrade_websocket = AsyncMock()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_CONNECTION': 'Foo,Upgrade,Bar',
- 'HTTP_UPGRADE': 'websocket'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_CONNECTION': 'Foo,Upgrade,Bar',
+ 'HTTP_UPGRADE': 'websocket',
+ }
_run(s.handle_get_request(environ))
s._upgrade_websocket.mock.assert_called_once_with(environ)
@@ -199,7 +251,8 @@ class TestSocket(unittest.TestCase):
environ = "foo"
_run(s._upgrade_websocket(environ))
mock_server._async['websocket'].assert_called_once_with(
- s._websocket_handler)
+ s._websocket_handler
+ )
mock_ws.mock.assert_called_once_with(environ)
def test_upgrade_twice(self):
@@ -209,7 +262,8 @@ class TestSocket(unittest.TestCase):
s.connected = True
s.upgraded = True
environ = "foo"
- self.assertRaises(IOError, _run, s._upgrade_websocket(environ))
+ with pytest.raises(IOError):
+ _run(s._upgrade_websocket(environ))
def test_upgrade_packet(self):
mock_server = self._get_mock_server()
@@ -217,8 +271,8 @@ class TestSocket(unittest.TestCase):
s.connected = True
_run(s.receive(packet.Packet(packet.UPGRADE)))
r = _run(s.poll())
- self.assertEqual(len(r), 1)
- self.assertEqual(r[0].encode(), packet.Packet(packet.NOOP).encode())
+ assert len(r) == 1
+ assert r[0].encode() == packet.Packet(packet.NOOP).encode()
def test_upgrade_no_probe(self):
mock_server = self._get_mock_server()
@@ -226,10 +280,9 @@ class TestSocket(unittest.TestCase):
s.connected = True
ws = mock.MagicMock()
ws.wait = AsyncMock()
- ws.wait.mock.return_value = packet.Packet(packet.NOOP).encode(
- always_bytes=False)
+ ws.wait.mock.return_value = packet.Packet(packet.NOOP).encode()
_run(s._websocket_handler(ws))
- self.assertFalse(s.upgraded)
+ assert not s.upgraded
def test_upgrade_no_upgrade_packet(self):
mock_server = self._get_mock_server()
@@ -239,16 +292,17 @@ class TestSocket(unittest.TestCase):
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
- probe = six.text_type('probe')
+ probe = 'probe'
ws.wait.mock.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.NOOP).encode(always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.NOOP).encode(),
+ ]
_run(s._websocket_handler(ws))
- ws.send.mock.assert_called_once_with(packet.Packet(
- packet.PONG, data=probe).encode(always_bytes=False))
- self.assertEqual(_run(s.queue.get()).packet_type, packet.NOOP)
- self.assertFalse(s.upgraded)
+ ws.send.mock.assert_called_once_with(
+ packet.Packet(packet.PONG, data=probe).encode()
+ )
+ assert _run(s.queue.get()).packet_type == packet.NOOP
+ assert not s.upgraded
def test_upgrade_not_supported(self):
mock_server = self._get_mock_server()
@@ -272,24 +326,28 @@ class TestSocket(unittest.TestCase):
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = False
s.queue.join = AsyncMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = AsyncMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], None])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = AsyncMock(
+ side_effect=[[packet.Packet(packet.MESSAGE, data=bar)], None]
+ )
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
_run(s._websocket_handler(ws))
- self.assertTrue(s.connected)
- self.assertTrue(s.upgraded)
- self.assertEqual(mock_server._trigger_event.mock.call_count, 2)
- mock_server._trigger_event.mock.assert_has_calls([
- mock.call('message', 'sid', 'foo', run_async=False),
- mock.call('disconnect', 'sid')])
+ assert s.connected
+ assert s.upgraded
+ assert mock_server._trigger_event.mock.call_count == 2
+ mock_server._trigger_event.mock.assert_has_calls(
+ [
+ mock.call('message', 'sid', 'foo', run_async=False),
+ mock.call('disconnect', 'sid'),
+ ]
+ )
ws.send.mock.assert_called_with('4bar')
def test_websocket_upgrade_read_write(self):
@@ -297,27 +355,33 @@ class TestSocket(unittest.TestCase):
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = True
s.queue.join = AsyncMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- probe = six.text_type('probe')
- s.poll = AsyncMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ probe = 'probe'
+ s.poll = AsyncMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE).encode(always_bytes=False),
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE).encode(),
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
_run(s._websocket_handler(ws))
- self.assertTrue(s.upgraded)
- self.assertEqual(mock_server._trigger_event.mock.call_count, 2)
- mock_server._trigger_event.mock.assert_has_calls([
- mock.call('message', 'sid', 'foo', run_async=False),
- mock.call('disconnect', 'sid')])
+ assert s.upgraded
+ assert mock_server._trigger_event.mock.call_count == 2
+ mock_server._trigger_event.mock.assert_has_calls(
+ [
+ mock.call('message', 'sid', 'foo', run_async=False),
+ mock.call('disconnect', 'sid'),
+ ]
+ )
ws.send.mock.assert_called_with('4bar')
def test_websocket_upgrade_with_payload(self):
@@ -325,104 +389,123 @@ class TestSocket(unittest.TestCase):
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = True
s.queue.join = AsyncMock(return_value=None)
- probe = six.text_type('probe')
+ probe = 'probe'
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE, data=b'2').encode(
- always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE, data='2').encode(),
+ ]
_run(s._websocket_handler(ws))
- self.assertTrue(s.upgraded)
+ assert s.upgraded
def test_websocket_upgrade_with_backlog(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = True
s.queue.join = AsyncMock(return_value=None)
- probe = six.text_type('probe')
- foo = six.text_type('foo')
+ probe = 'probe'
+ foo = 'foo'
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE, data=b'2').encode(
- always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE, data='2').encode(),
+ ]
s.upgrading = True
_run(s.send(packet.Packet(packet.MESSAGE, data=foo)))
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=sid'}
+ packets = _run(s.handle_get_request(environ))
+ assert len(packets) == 1
+ assert packets[0].encode() == '6'
+ packets = _run(s.poll())
+ assert len(packets) == 1
+ assert packets[0].encode() == '4foo'
+
_run(s._websocket_handler(ws))
- self.assertTrue(s.upgraded)
- self.assertFalse(s.upgrading)
- self.assertEqual(s.packet_backlog, [])
- ws.send.mock.assert_called_with('4foo')
+ assert s.upgraded
+ assert not s.upgrading
+ packets = _run(s.handle_get_request(environ))
+ assert len(packets) == 1
+ assert packets[0].encode() == '6'
def test_websocket_read_write_wait_fail(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = False
s.queue.join = AsyncMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = AsyncMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)],
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = AsyncMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- RuntimeError]
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ RuntimeError,
+ ]
ws.send.mock.side_effect = [None, RuntimeError]
_run(s._websocket_handler(ws))
- self.assertEqual(s.closed, True)
+ assert s.closed
def test_websocket_ignore_invalid_packet(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
s.connected = False
s.queue.join = AsyncMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = AsyncMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = AsyncMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.send = AsyncMock()
ws.wait = AsyncMock()
ws.wait.mock.side_effect = [
- packet.Packet(packet.OPEN).encode(always_bytes=False),
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.OPEN).encode(),
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
_run(s._websocket_handler(ws))
- self.assertTrue(s.connected)
- self.assertEqual(mock_server._trigger_event.mock.call_count, 2)
- mock_server._trigger_event.mock.assert_has_calls([
- mock.call('message', 'sid', foo, run_async=False),
- mock.call('disconnect', 'sid')])
+ assert s.connected
+ assert mock_server._trigger_event.mock.call_count == 2
+ mock_server._trigger_event.mock.assert_has_calls(
+ [
+ mock.call('message', 'sid', foo, run_async=False),
+ mock.call('disconnect', 'sid'),
+ ]
+ )
ws.send.mock.assert_called_with('4bar')
def test_send_after_close(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
_run(s.close(wait=False))
- self.assertRaises(exceptions.SocketIsClosedError, _run,
- s.send(packet.Packet(packet.NOOP)))
+ with pytest.raises(exceptions.SocketIsClosedError):
+ _run(s.send(packet.Packet(packet.NOOP)))
def test_close_after_close(self):
mock_server = self._get_mock_server()
s = asyncio_socket.AsyncSocket(mock_server, 'sid')
_run(s.close(wait=False))
- self.assertTrue(s.closed)
- self.assertEqual(mock_server._trigger_event.mock.call_count, 1)
- mock_server._trigger_event.mock.assert_called_once_with('disconnect',
- 'sid')
+ assert s.closed
+ assert mock_server._trigger_event.mock.call_count == 1
+ mock_server._trigger_event.mock.assert_called_once_with(
+ 'disconnect', 'sid'
+ )
_run(s.close())
- self.assertEqual(mock_server._trigger_event.mock.call_count, 1)
+ assert mock_server._trigger_event.mock.call_count == 1
def test_close_and_wait(self):
mock_server = self._get_mock_server()
@@ -440,4 +523,4 @@ class TestSocket(unittest.TestCase):
s.queue.put = AsyncMock()
s.queue.join = AsyncMock()
_run(s.close(wait=False))
- self.assertEqual(s.queue.join.mock.call_count, 0)
+ assert s.queue.join.mock.call_count == 0
diff --git a/debian/tests/common/test_async_eventlet.py b/debian/tests/common/test_async_eventlet.py
index c516094..2509677 100644
--- a/debian/tests/common/test_async_eventlet.py
+++ b/debian/tests/common/test_async_eventlet.py
@@ -1,13 +1,9 @@
import logging
import unittest
-
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+from unittest import mock
from engineio.async_drivers import eventlet as async_eventlet
+import pytest
class TestAsyncEventlet(unittest.TestCase):
@@ -18,13 +14,16 @@ class TestAsyncEventlet(unittest.TestCase):
wsgi = async_eventlet.WebSocketWSGI(None)
environ = {'foo': 'bar'}
start_response = 'bar'
- self.assertRaises(RuntimeError, wsgi, environ, start_response)
+ with pytest.raises(RuntimeError):
+ wsgi(environ, start_response)
- @mock.patch('engineio.async_drivers.eventlet._WebSocketWSGI.__call__',
- return_value='data')
+ @mock.patch(
+ 'engineio.async_drivers.eventlet._WebSocketWSGI.__call__',
+ return_value='data',
+ )
def test_wsgi_call(self, _WebSocketWSGI):
_WebSocketWSGI.__call__ = lambda e, s: 'data'
environ = {'eventlet.input': mock.MagicMock()}
start_response = 'bar'
wsgi = async_eventlet.WebSocketWSGI(None)
- self.assertEqual(wsgi(environ, start_response), 'data')
+ assert wsgi(environ, start_response) == 'data'
diff --git a/debian/tests/common/test_client.py b/debian/tests/common/test_client.py
index 63a579a..532c078 100644
--- a/debian/tests/common/test_client.py
+++ b/debian/tests/common/test_client.py
@@ -3,12 +3,9 @@ import logging
import ssl
import time
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+import pytest
import websocket
from engineio import client
@@ -16,51 +13,57 @@ from engineio import exceptions
from engineio import packet
from engineio import payload
-if six.PY2:
- ConnectionError = OSError
-
class TestClient(unittest.TestCase):
def test_is_asyncio_based(self):
c = client.Client()
- self.assertEqual(c.is_asyncio_based(), False)
+ assert not c.is_asyncio_based()
def test_create(self):
c = client.Client()
- self.assertEqual(c.handlers, {})
- for attr in ['base_url', 'transports', 'sid', 'upgrades',
- 'ping_interval', 'ping_timeout', 'http', 'ws',
- 'read_loop_task', 'write_loop_task', 'queue']:
- self.assertIsNone(getattr(c, attr), attr + ' is not None')
- self.assertTrue(c.pong_received)
- self.assertEqual(c.state, 'disconnected')
+ assert c.handlers == {}
+ for attr in [
+ 'base_url',
+ 'transports',
+ 'sid',
+ 'upgrades',
+ 'ping_interval',
+ 'ping_timeout',
+ 'http',
+ 'ws',
+ 'read_loop_task',
+ 'write_loop_task',
+ 'queue',
+ ]:
+ assert getattr(c, attr) is None, attr + ' is not None'
+ assert c.state == 'disconnected'
def test_custon_json(self):
client.Client()
- self.assertEqual(packet.Packet.json, json)
+ assert packet.Packet.json == json
client.Client(json='foo')
- self.assertEqual(packet.Packet.json, 'foo')
+ assert packet.Packet.json == 'foo'
packet.Packet.json = json
def test_logger(self):
c = client.Client(logger=False)
- self.assertEqual(c.logger.getEffectiveLevel(), logging.ERROR)
+ assert c.logger.getEffectiveLevel() == logging.ERROR
c.logger.setLevel(logging.NOTSET)
c = client.Client(logger=True)
- self.assertEqual(c.logger.getEffectiveLevel(), logging.INFO)
+ assert c.logger.getEffectiveLevel() == logging.INFO
c.logger.setLevel(logging.WARNING)
c = client.Client(logger=True)
- self.assertEqual(c.logger.getEffectiveLevel(), logging.WARNING)
+ assert c.logger.getEffectiveLevel() == logging.WARNING
c.logger.setLevel(logging.NOTSET)
my_logger = logging.Logger('foo')
c = client.Client(logger=my_logger)
- self.assertEqual(c.logger, my_logger)
+ assert c.logger == my_logger
def test_custon_timeout(self):
c = client.Client()
- self.assertEqual(c.request_timeout, 5)
+ assert c.request_timeout == 5
c = client.Client(request_timeout=27)
- self.assertEqual(c.request_timeout, 27)
+ assert c.request_timeout == 27
def test_on_event(self):
c = client.Client()
@@ -68,82 +71,89 @@ class TestClient(unittest.TestCase):
@c.on('connect')
def foo():
pass
+
c.on('disconnect', foo)
- self.assertEqual(c.handlers['connect'], foo)
- self.assertEqual(c.handlers['disconnect'], foo)
+ assert c.handlers['connect'] == foo
+ assert c.handlers['disconnect'] == foo
def test_on_event_invalid(self):
c = client.Client()
- self.assertRaises(ValueError, c.on, 'invalid')
+ with pytest.raises(ValueError):
+ c.on('invalid')
def test_already_connected(self):
c = client.Client()
c.state = 'connected'
- self.assertRaises(ValueError, c.connect, 'http://foo')
+ with pytest.raises(ValueError):
+ c.connect('http://foo')
def test_invalid_transports(self):
c = client.Client()
- self.assertRaises(ValueError, c.connect, 'http://foo',
- transports=['foo', 'bar'])
+ with pytest.raises(ValueError):
+ c.connect('http://foo', transports=['foo', 'bar'])
def test_some_invalid_transports(self):
c = client.Client()
c._connect_websocket = mock.MagicMock()
c.connect('http://foo', transports=['foo', 'websocket', 'bar'])
- self.assertEqual(c.transports, ['websocket'])
+ assert c.transports == ['websocket']
def test_connect_polling(self):
c = client.Client()
c._connect_polling = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo'), 'foo')
+ assert c.connect('http://foo') == 'foo'
c._connect_polling.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = client.Client()
c._connect_polling = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo', transports=['polling']),
- 'foo')
+ assert c.connect('http://foo', transports=['polling']) == 'foo'
c._connect_polling.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = client.Client()
c._connect_polling = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo',
- transports=['polling', 'websocket']),
- 'foo')
+ assert (
+ c.connect('http://foo', transports=['polling', 'websocket'])
+ == 'foo'
+ )
c._connect_polling.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
def test_connect_websocket(self):
c = client.Client()
c._connect_websocket = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo', transports=['websocket']),
- 'foo')
+ assert c.connect('http://foo', transports=['websocket']) == 'foo'
c._connect_websocket.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
c = client.Client()
c._connect_websocket = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo', transports='websocket'),
- 'foo')
+ assert c.connect('http://foo', transports='websocket') == 'foo'
c._connect_websocket.assert_called_once_with(
- 'http://foo', {}, 'engine.io')
+ 'http://foo', {}, 'engine.io'
+ )
def test_connect_query_string(self):
c = client.Client()
c._connect_polling = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo?bar=baz'), 'foo')
+ assert c.connect('http://foo?bar=baz') == 'foo'
c._connect_polling.assert_called_once_with(
- 'http://foo?bar=baz', {}, 'engine.io')
+ 'http://foo?bar=baz', {}, 'engine.io'
+ )
def test_connect_custom_headers(self):
c = client.Client()
c._connect_polling = mock.MagicMock(return_value='foo')
- self.assertEqual(c.connect('http://foo', headers={'Foo': 'Bar'}),
- 'foo')
+ assert c.connect('http://foo', headers={'Foo': 'Bar'}) == 'foo'
c._connect_polling.assert_called_once_with(
- 'http://foo', {'Foo': 'Bar'}, 'engine.io')
+ 'http://foo', {'Foo': 'Bar'}, 'engine.io'
+ )
def test_wait(self):
c = client.Client()
@@ -165,26 +175,25 @@ class TestClient(unittest.TestCase):
c._send_packet = fake_send_packet
c.send('foo')
- c.send('foo', binary=False)
- c.send(b'foo', binary=True)
- self.assertEqual(saved_packets[0].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[0].data, 'foo')
- self.assertEqual(saved_packets[0].binary,
- False if six.PY3 else True)
- self.assertEqual(saved_packets[1].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[1].data, 'foo')
- self.assertEqual(saved_packets[1].binary, False)
- self.assertEqual(saved_packets[2].packet_type, packet.MESSAGE)
- self.assertEqual(saved_packets[2].data, b'foo')
- self.assertEqual(saved_packets[2].binary, True)
+ c.send('foo')
+ c.send(b'foo')
+ assert saved_packets[0].packet_type == packet.MESSAGE
+ assert saved_packets[0].data == 'foo'
+ assert not saved_packets[0].binary
+ assert saved_packets[1].packet_type == packet.MESSAGE
+ assert saved_packets[1].data == 'foo'
+ assert not saved_packets[1].binary
+ assert saved_packets[2].packet_type == packet.MESSAGE
+ assert saved_packets[2].data == b'foo'
+ assert saved_packets[2].binary
def test_disconnect_not_connected(self):
c = client.Client()
c.state = 'foo'
c.sid = 'bar'
c.disconnect()
- self.assertEqual(c.state, 'disconnected')
- self.assertIsNone(c.sid)
+ assert c.state == 'disconnected'
+ assert c.sid is None
def test_disconnect_polling(self):
c = client.Client()
@@ -198,9 +207,8 @@ class TestClient(unittest.TestCase):
c.disconnect()
c.read_loop_task.join.assert_called_once_with()
c.ws.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
- c._trigger_event.assert_called_once_with('disconnect',
- run_async=False)
+ assert c not in client.connected_clients
+ c._trigger_event.assert_called_once_with('disconnect', run_async=False)
def test_disconnect_websocket(self):
c = client.Client()
@@ -214,9 +222,8 @@ class TestClient(unittest.TestCase):
c.disconnect()
c.read_loop_task.join.assert_called_once_with()
c.ws.close.assert_called_once_with()
- self.assertNotIn(c, client.connected_clients)
- c._trigger_event.assert_called_once_with('disconnect',
- run_async=False)
+ assert c not in client.connected_clients
+ c._trigger_event.assert_called_once_with('disconnect', run_async=False)
def test_disconnect_polling_abort(self):
c = client.Client()
@@ -230,7 +237,7 @@ class TestClient(unittest.TestCase):
c.queue.join.assert_not_called()
c.read_loop_task.join.assert_not_called()
c.ws.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
+ assert c not in client.connected_clients
def test_disconnect_websocket_abort(self):
c = client.Client()
@@ -244,12 +251,12 @@ class TestClient(unittest.TestCase):
c.queue.join.assert_not_called()
c.read_loop_task.join.assert_not_called()
c.ws.mock.assert_not_called()
- self.assertNotIn(c, client.connected_clients)
+ assert c not in client.connected_clients
def test_current_transport(self):
c = client.Client()
c.current_transport = 'foo'
- self.assertEqual(c.transport(), 'foo')
+ assert c.transport() == 'foo'
def test_background_tasks(self):
flag = {}
@@ -260,73 +267,115 @@ class TestClient(unittest.TestCase):
c = client.Client()
task = c.start_background_task(bg_task)
task.join()
- self.assertIn('task', flag)
- self.assertTrue(flag['task'])
+ assert 'task' in flag
+ assert flag['task']
def test_sleep(self):
c = client.Client()
t = time.time()
c.sleep(0.1)
- self.assertTrue(time.time() - t > 0.1)
+ assert time.time() - t > 0.1
def test_create_queue(self):
c = client.Client()
q = c.create_queue()
- self.assertRaises(q.Empty, q.get, timeout=0.01)
+ with pytest.raises(q.Empty):
+ q.get(timeout=0.01)
def test_create_event(self):
c = client.Client()
e = c.create_event()
- self.assertFalse(e.is_set())
+ assert not e.is_set()
e.set()
- self.assertTrue(e.is_set())
+ assert e.is_set()
@mock.patch('engineio.client.time.time', return_value=123.456)
@mock.patch('engineio.client.Client._send_request', return_value=None)
def test_polling_connection_failed(self, _send_request, _time):
c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
- headers={'Foo': 'Bar'})
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect('http://foo', headers={'Foo': 'Bar'})
_send_request.assert_called_once_with(
- 'GET', 'http://foo/engine.io/?transport=polling&EIO=3&t=123.456',
- headers={'Foo': 'Bar'}, timeout=5)
+ 'GET',
+ 'http://foo/engine.io/?transport=polling&EIO=4&t=123.456',
+ headers={'Foo': 'Bar'},
+ timeout=5,
+ )
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_404(self, _send_request):
_send_request.return_value.status_code = 404
+ _send_request.return_value.json.return_value = {'foo': 'bar'}
c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
+ try:
+ c.connect('http://foo')
+ except exceptions.ConnectionError as exc:
+ assert len(exc.args) == 2
+ assert (
+ exc.args[0] == 'Unexpected status code 404 in server response'
+ )
+ assert exc.args[1] == {'foo': 'bar'}
+
+ @mock.patch('engineio.client.Client._send_request')
+ def test_polling_connection_404_no_json(self, _send_request):
+ _send_request.return_value.status_code = 404
+ _send_request.return_value.json.side_effect = json.JSONDecodeError(
+ 'error', '<html></html>', 0)
+ c = client.Client()
+ try:
+ c.connect('http://foo')
+ except exceptions.ConnectionError as exc:
+ assert len(exc.args) == 2
+ assert (
+ exc.args[0] == 'Unexpected status code 404 in server response'
+ )
+ assert exc.args[1] is None
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_invalid_packet(self, _send_request):
_send_request.return_value.status_code = 200
_send_request.return_value.content = b'foo'
c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect('http://foo')
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_no_open_packet(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.CLOSE, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 10,
- 'pingTimeout': 20
- })
- ]).encode()
- c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.CLOSE,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 10,
+ 'pingTimeout': 20,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ c = client.Client()
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect('http://foo')
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_successful(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode()
- c = client.Client()
- c._ping_loop = mock.MagicMock()
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
+ c = client.Client()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -335,32 +384,38 @@ class TestClient(unittest.TestCase):
c.connect('http://foo')
time.sleep(0.1)
- c._ping_loop.assert_called_once_with()
c._read_loop_polling.assert_called_once_with()
c._read_loop_websocket.assert_not_called()
c._write_loop.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'polling')
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'polling'
@mock.patch('engineio.client.Client._send_request')
def test_polling_https_noverify_connection_successful(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode()
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
c = client.Client(ssl_verify=False)
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -369,33 +424,39 @@ class TestClient(unittest.TestCase):
c.connect('https://foo')
time.sleep(0.1)
- c._ping_loop.assert_called_once_with()
c._read_loop_polling.assert_called_once_with()
c._read_loop_websocket.assert_not_called()
c._write_loop.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'https://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'polling')
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'https://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'polling'
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_with_more_packets(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }),
- packet.Packet(packet.NOOP)
- ]).encode()
- c = client.Client()
- c._ping_loop = mock.MagicMock()
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ),
+ packet.Packet(packet.NOOP),
+ ]
+ ).encode().encode('utf-8')
+ c = client.Client()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -404,50 +465,66 @@ class TestClient(unittest.TestCase):
c.on('connect', on_connect)
c.connect('http://foo')
time.sleep(0.1)
- self.assertEqual(c._receive_packet.call_count, 1)
- self.assertEqual(
- c._receive_packet.call_args_list[0][0][0].packet_type,
- packet.NOOP)
+ assert c._receive_packet.call_count == 1
+ assert (
+ c._receive_packet.call_args_list[0][0][0].packet_type
+ == packet.NOOP
+ )
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_upgraded(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode()
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': ['websocket'],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
c = client.Client()
c._connect_websocket = mock.MagicMock(return_value=True)
on_connect = mock.MagicMock()
c.on('connect', on_connect)
c.connect('http://foo')
- c._connect_websocket.assert_called_once_with('http://foo', {},
- 'engine.io')
+ c._connect_websocket.assert_called_once_with(
+ 'http://foo', {}, 'engine.io'
+ )
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, ['websocket'])
+ assert c in client.connected_clients
+ assert (
+ c.base_url
+ == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123'
+ )
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == ['websocket']
@mock.patch('engineio.client.Client._send_request')
def test_polling_connection_not_upgraded(self, _send_request):
_send_request.return_value.status_code = 200
- _send_request.return_value.content = payload.Payload(packets=[
- packet.Packet(packet.OPEN, {
- 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000,
- 'pingTimeout': 2000
- })
- ]).encode()
+ _send_request.return_value.content = payload.Payload(
+ packets=[
+ packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': ['websocket'],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ )
+ ]
+ ).encode().encode('utf-8')
c = client.Client()
c._connect_websocket = mock.MagicMock(return_value=False)
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -456,54 +533,92 @@ class TestClient(unittest.TestCase):
c.connect('http://foo')
time.sleep(0.1)
- c._connect_websocket.assert_called_once_with('http://foo', {},
- 'engine.io')
- c._ping_loop.assert_called_once_with()
+ c._connect_websocket.assert_called_once_with(
+ 'http://foo', {}, 'engine.io'
+ )
c._read_loop_polling.assert_called_once_with()
c._read_loop_websocket.assert_not_called()
c._write_loop.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
+ assert c in client.connected_clients
@mock.patch('engineio.client.time.time', return_value=123.456)
- @mock.patch('engineio.client.websocket.create_connection',
- side_effect=[ConnectionError])
+ @mock.patch(
+ 'engineio.client.websocket.create_connection',
+ side_effect=[ConnectionError],
+ )
def test_websocket_connection_failed(self, create_connection, _time):
c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
- transports=['websocket'], headers={'Foo': 'Bar'})
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect(
+ 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'}
+ )
create_connection.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&t=123.456',
- header={'Foo': 'Bar'}, cookie=None)
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ header={'Foo': 'Bar'},
+ cookie=None,
+ enable_multithread=True,
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
- @mock.patch('engineio.client.websocket.create_connection',
- side_effect=[ConnectionError])
+ @mock.patch(
+ 'engineio.client.websocket.create_connection',
+ side_effect=[websocket.WebSocketException],
+ )
+ def test_websocket_connection_failed_with_websocket_error(
+ self, create_connection, _time
+ ):
+ c = client.Client()
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect(
+ 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'}
+ )
+ create_connection.assert_called_once_with(
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456',
+ header={'Foo': 'Bar'},
+ cookie=None,
+ enable_multithread=True,
+ )
+
+ @mock.patch('engineio.client.time.time', return_value=123.456)
+ @mock.patch(
+ 'engineio.client.websocket.create_connection',
+ side_effect=[ConnectionError],
+ )
def test_websocket_upgrade_failed(self, create_connection, _time):
c = client.Client()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.sid = '123'
- self.assertFalse(c.connect('http://foo', transports=['websocket']))
+ assert not c.connect('http://foo', transports=['websocket'])
create_connection.assert_called_once_with(
- 'ws://foo/engine.io/?transport=websocket&EIO=3&sid=123&t=123.456',
- header={}, cookie=None)
+ 'ws://foo/engine.io/?transport=websocket&EIO=4&sid=123&t=123.456',
+ header={},
+ cookie=None,
+ enable_multithread=True,
+ )
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_no_open_packet(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.CLOSE).encode()
+ packet.CLOSE
+ ).encode()
c = client.Client()
- self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
- transports=['websocket'])
+ with pytest.raises(exceptions.ConnectionError):
+ c.connect('http://foo', transports=['websocket'])
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_successful(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c = client.Client()
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -512,35 +627,39 @@ class TestClient(unittest.TestCase):
c.connect('ws://foo', transports=['websocket'])
time.sleep(0.1)
- c._ping_loop.assert_called_once_with()
c._read_loop_polling.assert_not_called()
c._read_loop_websocket.assert_called_once_with()
c._write_loop.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'ws://foo/engine.io/?transport=websocket&EIO=3')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, create_connection.return_value)
- self.assertEqual(len(create_connection.call_args_list), 1)
- self.assertEqual(create_connection.call_args[1],
- {'header': {}, 'cookie': None})
+ assert c in client.connected_clients
+ assert c.base_url == 'ws://foo/engine.io/?transport=websocket&EIO=4'
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'websocket'
+ assert c.ws == create_connection.return_value
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {},
+ 'cookie': None,
+ 'enable_multithread': True,
+ }
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_https_noverify_connection_successful(
- self, create_connection):
+ self, create_connection
+ ):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c = client.Client(ssl_verify=False)
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -549,41 +668,47 @@ class TestClient(unittest.TestCase):
c.connect('wss://foo', transports=['websocket'])
time.sleep(0.1)
- c._ping_loop.assert_called_once_with()
c._read_loop_polling.assert_not_called()
c._read_loop_websocket.assert_called_once_with()
c._write_loop.assert_called_once_with()
on_connect.assert_called_once_with()
- self.assertIn(c, client.connected_clients)
- self.assertEqual(
- c.base_url,
- 'wss://foo/engine.io/?transport=websocket&EIO=3')
- self.assertEqual(c.sid, '123')
- self.assertEqual(c.ping_interval, 1)
- self.assertEqual(c.ping_timeout, 2)
- self.assertEqual(c.upgrades, [])
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, create_connection.return_value)
- self.assertEqual(len(create_connection.call_args_list), 1)
- self.assertEqual(create_connection.call_args[1],
- {'header': {}, 'cookie': None,
- 'sslopt': {'cert_reqs': ssl.CERT_NONE}})
+ assert c in client.connected_clients
+ assert c.base_url == 'wss://foo/engine.io/?transport=websocket&EIO=4'
+ assert c.sid == '123'
+ assert c.ping_interval == 1
+ assert c.ping_timeout == 2
+ assert c.upgrades == []
+ assert c.transport() == 'websocket'
+ assert c.ws == create_connection.return_value
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {},
+ 'cookie': None,
+ 'enable_multithread': True,
+ 'sslopt': {'cert_reqs': ssl.CERT_NONE},
+ }
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_with_cookies(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
- c = client.Client()
- c.http = mock.MagicMock()
- c.http.cookies = [mock.MagicMock(), mock.MagicMock()]
- c.http.cookies[0].name = 'key'
- c.http.cookies[0].value = 'value'
- c.http.cookies[1].name = 'key2'
- c.http.cookies[1].value = 'value2'
- c._ping_loop = mock.MagicMock()
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = [mock.MagicMock(), mock.MagicMock()]
+ http.cookies[0].name = 'key'
+ http.cookies[0].value = 'value'
+ http.cookies[1].name = 'key2'
+ http.cookies[1].value = 'value2'
+ http.auth = None
+ http.proxies = None
+ http.cert = None
+ c = client.Client(http_session=http)
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
@@ -592,73 +717,349 @@ class TestClient(unittest.TestCase):
c.connect('ws://foo', transports=['websocket'])
time.sleep(0.1)
- self.assertEqual(len(create_connection.call_args_list), 1)
- self.assertEqual(create_connection.call_args[1],
- {'header': {}, 'cookie': 'key=value; key2=value2'})
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {},
+ 'cookie': 'key=value; key2=value2',
+ 'enable_multithread': True,
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_cookie_header(self, create_connection):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = None
+ http.proxies = None
+ http.cert = None
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect(
+ 'ws://foo',
+ headers={'Foo': 'bar', 'Cookie': 'key=value'},
+ transports=['websocket'],
+ )
+ time.sleep(0.1)
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {'Foo': 'bar'},
+ 'cookie': 'key=value',
+ 'enable_multithread': True,
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_cookies_and_headers(
+ self, create_connection
+ ):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = [mock.MagicMock(), mock.MagicMock()]
+ http.cookies[0].name = 'key'
+ http.cookies[0].value = 'value'
+ http.cookies[1].name = 'key2'
+ http.cookies[1].value = 'value2'
+ http.auth = None
+ http.proxies = None
+ http.cert = None
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect(
+ 'ws://foo',
+ headers={'Cookie': 'key3=value3'},
+ transports=['websocket'],
+ )
+ time.sleep(0.1)
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {},
+ 'enable_multithread': True,
+ 'cookie': 'key=value; key2=value2; key3=value3',
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_auth(self, create_connection):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = ('foo', 'bar')
+ http.proxies = None
+ http.cert = None
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect('ws://foo', transports=['websocket'])
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'header': {'Authorization': 'Basic Zm9vOmJhcg=='},
+ 'cookie': '',
+ 'enable_multithread': True,
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_cert(self, create_connection):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = None
+ http.proxies = None
+ http.cert = 'foo.crt'
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect('ws://foo', transports=['websocket'])
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'sslopt': {'certfile': 'foo.crt'},
+ 'header': {},
+ 'cookie': '',
+ 'enable_multithread': True,
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_cert_and_key(self, create_connection):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = None
+ http.proxies = None
+ http.cert = ('foo.crt', 'key.pem')
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect('ws://foo', transports=['websocket'])
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'sslopt': {'certfile': 'foo.crt', 'keyfile': 'key.pem'},
+ 'header': {},
+ 'cookie': '',
+ 'enable_multithread': True,
+ }
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_with_proxies(self, create_connection):
+ all_urls = [
+ 'ws://foo',
+ 'ws://foo',
+ 'ws://foo',
+ 'ws://foo',
+ 'ws://foo',
+ 'wss://foo',
+ 'wss://foo',
+ ]
+ all_proxies = [
+ {'http': 'foo.com:1234'},
+ {'https': 'foo.com:1234'},
+ {'http': 'foo.com:1234', 'ws': 'bar.com:4321'},
+ {},
+ {'http': 'user:pass@foo.com:1234'},
+ {'https': 'foo.com:1234'},
+ {'https': 'foo.com:1234', 'wss': 'bar.com:4321'},
+ ]
+ all_results = [
+ ('foo.com', 1234, None),
+ None,
+ ('bar.com', 4321, None),
+ None,
+ ('foo.com', 1234, ('user', 'pass')),
+ ('foo.com', 1234, None),
+ ('bar.com', 4321, None),
+ ]
+ for url, proxies, results in zip(all_urls, all_proxies, all_results):
+ create_connection.reset_mock()
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = None
+ http.proxies = proxies
+ http.cert = None
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect(url, transports=['websocket'])
+
+ assert len(create_connection.call_args_list) == 1
+ expected_results = {
+ 'header': {},
+ 'cookie': '',
+ 'enable_multithread': True,
+ }
+ if results:
+ expected_results.update({
+ 'http_proxy_host': results[0],
+ 'http_proxy_port': results[1],
+ 'http_proxy_auth': results[2]})
+ assert create_connection.call_args[1] == expected_results
+
+ @mock.patch('engineio.client.websocket.create_connection')
+ def test_websocket_connection_without_verify(self, create_connection):
+ create_connection.return_value.recv.return_value = packet.Packet(
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
+ http = mock.MagicMock()
+ http.cookies = []
+ http.auth = None
+ http.proxies = None
+ http.cert = None
+ http.verify = False
+ c = client.Client(http_session=http)
+ c._read_loop_polling = mock.MagicMock()
+ c._read_loop_websocket = mock.MagicMock()
+ c._write_loop = mock.MagicMock()
+ on_connect = mock.MagicMock()
+ c.on('connect', on_connect)
+ c.connect('ws://foo', transports=['websocket'])
+
+ assert len(create_connection.call_args_list) == 1
+ assert create_connection.call_args[1] == {
+ 'sslopt': {"cert_reqs": ssl.CERT_NONE},
+ 'header': {},
+ 'cookie': '',
+ 'enable_multithread': True,
+ }
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_upgrade_no_pong(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.OPEN, {
- 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
- 'pingTimeout': 2000
- }).encode()
+ packet.OPEN,
+ {
+ 'sid': '123',
+ 'upgrades': [],
+ 'pingInterval': 1000,
+ 'pingTimeout': 2000,
+ },
+ ).encode()
c = client.Client()
c.sid = '123'
c.current_transport = 'polling'
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
- self.assertFalse(c.connect('ws://foo', transports=['websocket']))
+ assert not c.connect('ws://foo', transports=['websocket'])
- c._ping_loop.assert_not_called()
c._read_loop_polling.assert_not_called()
c._read_loop_websocket.assert_not_called()
c._write_loop.assert_not_called()
on_connect.assert_not_called()
- self.assertEqual(c.transport(), 'polling')
+ assert c.transport() == 'polling'
@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_upgrade_successful(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
- packet.PONG, six.text_type('probe')).encode()
+ packet.PONG, 'probe').encode()
c = client.Client()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.sid = '123'
c.base_url = 'http://foo'
c.current_transport = 'polling'
- c._ping_loop = mock.MagicMock()
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
- self.assertTrue(c.connect('ws://foo', transports=['websocket']))
+ assert c.connect('ws://foo', transports=['websocket'])
time.sleep(0.1)
- c._ping_loop.assert_called_once_with()
c._read_loop_polling.assert_not_called()
c._read_loop_websocket.assert_called_once_with()
c._write_loop.assert_called_once_with()
on_connect.assert_not_called() # was called by polling
- self.assertNotIn(c, client.connected_clients) # was added by polling
- self.assertEqual(c.base_url, 'http://foo') # not changed
- self.assertEqual(c.sid, '123') # not changed
- self.assertEqual(c.transport(), 'websocket')
- self.assertEqual(c.ws, create_connection.return_value)
- self.assertEqual(
- create_connection.return_value.send.call_args_list[0],
- ((packet.Packet(packet.PING, six.text_type('probe')).encode(),),)
+ assert c not in client.connected_clients # was added by polling
+ assert c.base_url == 'http://foo' # not changed
+ assert c.sid == '123' # not changed
+ assert c.transport() == 'websocket'
+ assert c.ws == create_connection.return_value
+ assert create_connection.return_value.send.call_args_list[0] == (
+ (packet.Packet(packet.PING, 'probe').encode(),),
) # ping
- self.assertEqual(
- create_connection.return_value.send.call_args_list[1],
- ((packet.Packet(packet.UPGRADE).encode(),),)) # upgrade
+ assert create_connection.return_value.send.call_args_list[1] == (
+ (packet.Packet(packet.UPGRADE).encode(),),
+ ) # upgrade
def test_receive_unknown_packet(self):
c = client.Client()
- c._receive_packet(packet.Packet(encoded_packet=b'9'))
+ c._receive_packet(packet.Packet(encoded_packet='9'))
# should be ignored
def test_receive_noop_packet(self):
@@ -666,18 +1067,19 @@ class TestClient(unittest.TestCase):
c._receive_packet(packet.Packet(packet.NOOP))
# should be ignored
- def test_receive_pong_packet(self):
+ def test_receive_ping_packet(self):
c = client.Client()
- c.pong_received = False
- c._receive_packet(packet.Packet(packet.PONG))
- self.assertTrue(c.pong_received)
+ c._send_packet = mock.MagicMock()
+ c._receive_packet(packet.Packet(packet.PING))
+ assert c._send_packet.call_args_list[0][0][0].encode() == '3' # PONG
def test_receive_message_packet(self):
c = client.Client()
c._trigger_event = mock.MagicMock()
c._receive_packet(packet.Packet(packet.MESSAGE, {'foo': 'bar'}))
- c._trigger_event.assert_called_once_with('message', {'foo': 'bar'},
- run_async=True)
+ c._trigger_event.assert_called_once_with(
+ 'message', {'foo': 'bar'}, run_async=True
+ )
def test_receive_close_packet(self):
c = client.Client()
@@ -690,16 +1092,16 @@ class TestClient(unittest.TestCase):
c.queue = c.create_queue()
c.state = 'disconnected'
c._send_packet(packet.Packet(packet.NOOP))
- self.assertTrue(c.queue.empty())
+ assert c.queue.empty()
def test_send_packet(self):
c = client.Client()
c.queue = c.create_queue()
c.state = 'connected'
c._send_packet(packet.Packet(packet.NOOP))
- self.assertFalse(c.queue.empty())
+ assert not c.queue.empty()
pkt = c.queue.get()
- self.assertEqual(pkt.packet_type, packet.NOOP)
+ assert pkt.packet_type == packet.NOOP
def test_trigger_event(self):
c = client.Client()
@@ -715,12 +1117,12 @@ class TestClient(unittest.TestCase):
return 'bar'
r = c._trigger_event('connect', run_async=False)
- self.assertEqual(r, 'foo')
+ assert r == 'foo'
r = c._trigger_event('message', 123, run_async=True)
r.join()
- self.assertEqual(f['bar'], 123)
+ assert f['bar'] == 123
r = c._trigger_event('message', 321)
- self.assertEqual(r, 'bar')
+ assert r == 'bar'
def test_trigger_unknown_event(self):
c = client.Client()
@@ -740,116 +1142,52 @@ class TestClient(unittest.TestCase):
return 1 / 0
r = c._trigger_event('connect', run_async=False)
- self.assertEqual(r, None)
+ assert r is None
r = c._trigger_event('message', 123, run_async=False)
- self.assertEqual(r, None)
+ assert r is None
def test_engineio_url(self):
c = client.Client()
- self.assertEqual(
- c._get_engineio_url('http://foo', 'bar', 'polling'),
- 'http://foo/bar/?transport=polling&EIO=3')
- self.assertEqual(
- c._get_engineio_url('http://foo', 'bar', 'websocket'),
- 'ws://foo/bar/?transport=websocket&EIO=3')
- self.assertEqual(
- c._get_engineio_url('ws://foo', 'bar', 'polling'),
- 'http://foo/bar/?transport=polling&EIO=3')
- self.assertEqual(
- c._get_engineio_url('ws://foo', 'bar', 'websocket'),
- 'ws://foo/bar/?transport=websocket&EIO=3')
- self.assertEqual(
- c._get_engineio_url('https://foo', 'bar', 'polling'),
- 'https://foo/bar/?transport=polling&EIO=3')
- self.assertEqual(
- c._get_engineio_url('https://foo', 'bar', 'websocket'),
- 'wss://foo/bar/?transport=websocket&EIO=3')
- self.assertEqual(
- c._get_engineio_url('http://foo?baz=1', 'bar', 'polling'),
- 'http://foo/bar/?baz=1&transport=polling&EIO=3')
- self.assertEqual(
- c._get_engineio_url('http://foo#baz', 'bar', 'polling'),
- 'http://foo/bar/?transport=polling&EIO=3')
-
- def test_ping_loop_disconnected(self):
- c = client.Client()
- c.state = 'disconnected'
- c._ping_loop()
- # should not block
-
- def test_ping_loop_disconnect(self):
- c = client.Client()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = mock.MagicMock()
-
- states = [
- ('disconnecting', True)
- ]
-
- def fake_wait(timeout):
- self.assertEqual(timeout, 10)
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- c._ping_loop()
- self.assertEqual(
- c._send_packet.call_args_list[0][0][0].encode(), b'2')
-
- def test_ping_loop_missing_pong(self):
- c = client.Client()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = mock.MagicMock()
- c.queue = mock.MagicMock()
-
- states = [
- ('connected', False)
- ]
-
- def fake_wait(timeout):
- self.assertEqual(timeout, 10)
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- c._ping_loop()
- self.assertEqual(c.state, 'connected')
- c.queue.put.assert_called_once_with(None)
-
- def test_ping_loop_missing_pong_websocket(self):
- c = client.Client()
- c.state = 'connected'
- c.ping_interval = 10
- c._send_packet = mock.MagicMock()
- c.queue = mock.MagicMock()
- c.ws = mock.MagicMock()
-
- states = [
- ('connected', False)
- ]
-
- def fake_wait(timeout):
- self.assertEqual(timeout, 10)
- c.state, c.pong_received = states.pop(0)
-
- c.ping_loop_event = c.create_event()
- c.ping_loop_event.wait = fake_wait
- c._ping_loop()
- self.assertEqual(c.state, 'connected')
- c.queue.put.assert_called_once_with(None)
- c.ws.shutdown.assert_called_once_with()
+ assert (
+ c._get_engineio_url('http://foo', 'bar', 'polling')
+ == 'http://foo/bar/?transport=polling&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('http://foo', 'bar', 'websocket')
+ == 'ws://foo/bar/?transport=websocket&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('ws://foo', 'bar', 'polling')
+ == 'http://foo/bar/?transport=polling&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('ws://foo', 'bar', 'websocket')
+ == 'ws://foo/bar/?transport=websocket&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('https://foo', 'bar', 'polling')
+ == 'https://foo/bar/?transport=polling&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('https://foo', 'bar', 'websocket')
+ == 'wss://foo/bar/?transport=websocket&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('http://foo?baz=1', 'bar', 'polling')
+ == 'http://foo/bar/?baz=1&transport=polling&EIO=4'
+ )
+ assert (
+ c._get_engineio_url('http://foo#baz', 'bar', 'polling')
+ == 'http://foo/bar/?transport=polling&EIO=4'
+ )
def test_read_loop_polling_disconnected(self):
c = client.Client()
c.state = 'disconnected'
c._trigger_event = mock.MagicMock()
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_polling()
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
c._trigger_event.assert_not_called()
@mock.patch('engineio.client.time.time', return_value=123.456)
@@ -863,16 +1201,14 @@ class TestClient(unittest.TestCase):
c._send_request = mock.MagicMock(return_value=None)
c._trigger_event = mock.MagicMock()
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_polling()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
- c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456',
- timeout=30)
- c._trigger_event.assert_called_once_with('disconnect',
- run_async=False)
+ c._send_request.assert_called_once_with(
+ 'GET', 'http://foo&t=123.456', timeout=30
+ )
+ c._trigger_event.assert_called_once_with('disconnect', run_async=False)
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_read_loop_polling_bad_status(self, _time):
@@ -885,14 +1221,13 @@ class TestClient(unittest.TestCase):
c._send_request = mock.MagicMock()
c._send_request.return_value.status_code = 400
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_polling()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
- c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456',
- timeout=30)
+ c._send_request.assert_called_once_with(
+ 'GET', 'http://foo&t=123.456', timeout=30
+ )
@mock.patch('engineio.client.time.time', return_value=123.456)
def test_read_loop_polling_bad_packet(self, _time):
@@ -906,14 +1241,13 @@ class TestClient(unittest.TestCase):
c._send_request.return_value.status_code = 200
c._send_request.return_value.content = b'foo'
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_polling()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
- c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456',
- timeout=65)
+ c._send_request.assert_called_once_with(
+ 'GET', 'http://foo&t=123.456', timeout=65
+ )
def test_read_loop_polling(self):
c = client.Client()
@@ -924,32 +1258,45 @@ class TestClient(unittest.TestCase):
c.queue = mock.MagicMock()
c._send_request = mock.MagicMock()
c._send_request.side_effect = [
- mock.MagicMock(status_code=200, content=payload.Payload(packets=[
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP)]).encode()),
- None
+ mock.MagicMock(
+ status_code=200,
+ content=payload.Payload(
+ packets=[
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ ]
+ ).encode().encode('utf-8'),
+ ),
+ None,
]
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._receive_packet = mock.MagicMock()
c._read_loop_polling()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
- self.assertEqual(c._send_request.call_count, 2)
- self.assertEqual(c._receive_packet.call_count, 2)
- self.assertEqual(c._receive_packet.call_args_list[0][0][0].encode(),
- b'2')
- self.assertEqual(c._receive_packet.call_args_list[1][0][0].encode(),
- b'6')
+ assert c._send_request.call_count == 2
+ assert c._receive_packet.call_count == 2
+ assert c._receive_packet.call_args_list[0][0][0].encode() == '2'
+ assert c._receive_packet.call_args_list[1][0][0].encode() == '6'
def test_read_loop_websocket_disconnected(self):
c = client.Client()
c.state = 'disconnected'
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_websocket()
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
+
+ def test_read_loop_websocket_timeout(self):
+ c = client.Client()
+ c.state = 'connected'
+ c.queue = mock.MagicMock()
+ c.ws = mock.MagicMock()
+ c.ws.recv.side_effect = websocket.WebSocketTimeoutException
+ c.write_loop_task = mock.MagicMock()
+ c._read_loop_websocket()
+ assert c.state == 'disconnected'
+ c.queue.put.assert_called_once_with(None)
+ c.write_loop_task.join.assert_called_once_with()
def test_read_loop_websocket_no_response(self):
c = client.Client()
@@ -958,12 +1305,10 @@ class TestClient(unittest.TestCase):
c.ws = mock.MagicMock()
c.ws.recv.side_effect = websocket.WebSocketConnectionClosedException
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_websocket()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
def test_read_loop_websocket_unexpected_error(self):
c = client.Client()
@@ -972,30 +1317,29 @@ class TestClient(unittest.TestCase):
c.ws = mock.MagicMock()
c.ws.recv.side_effect = ValueError
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._read_loop_websocket()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
def test_read_loop_websocket(self):
c = client.Client()
+ c.ping_interval = 1
+ c.ping_timeout = 2
c.state = 'connected'
c.queue = mock.MagicMock()
c.ws = mock.MagicMock()
- c.ws.recv.side_effect = [packet.Packet(packet.PING).encode(),
- ValueError]
+ c.ws.recv.side_effect = [
+ packet.Packet(packet.PING).encode(),
+ ValueError,
+ ]
c.write_loop_task = mock.MagicMock()
- c.ping_loop_task = mock.MagicMock()
c._receive_packet = mock.MagicMock()
c._read_loop_websocket()
- self.assertEqual(c.state, 'disconnected')
+ assert c.state == 'disconnected'
c.queue.put.assert_called_once_with(None)
c.write_loop_task.join.assert_called_once_with()
- c.ping_loop_task.join.assert_called_once_with()
- self.assertEqual(c._receive_packet.call_args_list[0][0][0].encode(),
- b'2')
+ assert c._receive_packet.call_args_list[0][0][0].encode() == '2'
def test_write_loop_disconnected(self):
c = client.Client()
@@ -1037,17 +1381,22 @@ class TestClient(unittest.TestCase):
c.queue.get.side_effect = [
packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c._send_request = mock.MagicMock()
c._send_request.return_value.status_code = 200
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
def test_write_loop_polling_three_packets(self):
c = client.Client()
@@ -1063,20 +1412,26 @@ class TestClient(unittest.TestCase):
packet.Packet(packet.PING),
packet.Packet(packet.NOOP),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c._send_request = mock.MagicMock()
c._send_request.return_value.status_code = 200
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 3)
- p = payload.Payload(packets=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- packet.Packet(packet.PING),
- packet.Packet(packet.NOOP),
- ])
+ assert c.queue.task_done.call_count == 3
+ p = payload.Payload(
+ packets=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ packet.Packet(packet.PING),
+ packet.Packet(packet.NOOP),
+ ]
+ )
c._send_request.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
def test_write_loop_polling_two_packets_done(self):
c = client.Client()
@@ -1091,20 +1446,26 @@ class TestClient(unittest.TestCase):
packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
packet.Packet(packet.PING),
None,
- RuntimeError
+ RuntimeError,
]
c._send_request = mock.MagicMock()
c._send_request.return_value.status_code = 200
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 3)
- p = payload.Payload(packets=[
- packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
- packet.Packet(packet.PING),
- ])
+ assert c.queue.task_done.call_count == 3
+ p = payload.Payload(
+ packets=[
+ packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
+ packet.Packet(packet.PING),
+ ]
+ )
c._send_request.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'connected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'connected'
def test_write_loop_polling_bad_connection(self):
c = client.Client()
@@ -1122,13 +1483,18 @@ class TestClient(unittest.TestCase):
c._send_request = mock.MagicMock()
c._send_request.return_value = None
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'connected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'connected'
def test_write_loop_polling_bad_status(self):
c = client.Client()
@@ -1146,13 +1512,18 @@ class TestClient(unittest.TestCase):
c._send_request = mock.MagicMock()
c._send_request.return_value.status_code = 500
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 1)
+ assert c.queue.task_done.call_count == 1
p = payload.Payload(
- packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
+ packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})]
+ )
c._send_request.assert_called_once_with(
- 'POST', 'http://foo', body=p.encode(),
- headers={'Content-Type': 'application/octet-stream'}, timeout=5)
- self.assertEqual(c.state, 'disconnected')
+ 'POST',
+ 'http://foo',
+ body=p.encode(),
+ headers={'Content-Type': 'application/octet-stream'},
+ timeout=5,
+ )
+ assert c.state == 'disconnected'
def test_write_loop_websocket_one_packet(self):
c = client.Client()
@@ -1165,13 +1536,13 @@ class TestClient(unittest.TestCase):
c.queue.get.side_effect = [
packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c.ws = mock.MagicMock()
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 1)
- self.assertEqual(c.ws.send.call_count, 1)
- self.assertEqual(c.ws.send_binary.call_count, 0)
+ assert c.queue.task_done.call_count == 1
+ assert c.ws.send.call_count == 1
+ assert c.ws.send_binary.call_count == 0
c.ws.send.assert_called_once_with('4{"foo":"bar"}')
def test_write_loop_websocket_three_packets(self):
@@ -1187,16 +1558,16 @@ class TestClient(unittest.TestCase):
packet.Packet(packet.PING),
packet.Packet(packet.NOOP),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c.ws = mock.MagicMock()
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 3)
- self.assertEqual(c.ws.send.call_count, 3)
- self.assertEqual(c.ws.send_binary.call_count, 0)
- self.assertEqual(c.ws.send.call_args_list[0][0][0], '4{"foo":"bar"}')
- self.assertEqual(c.ws.send.call_args_list[1][0][0], '2')
- self.assertEqual(c.ws.send.call_args_list[2][0][0], '6')
+ assert c.queue.task_done.call_count == 3
+ assert c.ws.send.call_count == 3
+ assert c.ws.send_binary.call_count == 0
+ assert c.ws.send.call_args_list[0][0][0] == '4{"foo":"bar"}'
+ assert c.ws.send.call_args_list[1][0][0] == '2'
+ assert c.ws.send.call_args_list[2][0][0] == '6'
def test_write_loop_websocket_one_packet_binary(self):
c = client.Client()
@@ -1209,14 +1580,14 @@ class TestClient(unittest.TestCase):
c.queue.get.side_effect = [
packet.Packet(packet.MESSAGE, b'foo'),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c.ws = mock.MagicMock()
c._write_loop()
- self.assertEqual(c.queue.task_done.call_count, 1)
- self.assertEqual(c.ws.send.call_count, 0)
- self.assertEqual(c.ws.send_binary.call_count, 1)
- c.ws.send_binary.assert_called_once_with(b'\x04foo')
+ assert c.queue.task_done.call_count == 1
+ assert c.ws.send.call_count == 0
+ assert c.ws.send_binary.call_count == 1
+ c.ws.send_binary.assert_called_once_with(b'foo')
def test_write_loop_websocket_bad_connection(self):
c = client.Client()
@@ -1229,12 +1600,12 @@ class TestClient(unittest.TestCase):
c.queue.get.side_effect = [
packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
RuntimeError,
- RuntimeError
+ RuntimeError,
]
c.ws = mock.MagicMock()
c.ws.send.side_effect = websocket.WebSocketConnectionClosedException
c._write_loop()
- self.assertEqual(c.state, 'connected')
+ assert c.state == 'connected'
@mock.patch('engineio.client.original_signal_handler')
def test_signal_handler(self, original_handler):
@@ -1243,6 +1614,5 @@ class TestClient(unittest.TestCase):
client.connected_clients[0].is_asyncio_based.return_value = False
client.connected_clients[1].is_asyncio_based.return_value = True
client.signal_handler('sig', 'frame')
- clients[0].disconnect.assert_called_once_with(abort=True)
- clients[1].start_background_task.assert_called_once_with(
- clients[1].disconnect, abort=True)
+ clients[0].disconnect.assert_called_once_with()
+ clients[1].disconnect.assert_not_called()
diff --git a/debian/tests/common/test_middleware.py b/debian/tests/common/test_middleware.py
index 2e5a1e7..41c861d 100644
--- a/debian/tests/common/test_middleware.py
+++ b/debian/tests/common/test_middleware.py
@@ -1,11 +1,6 @@
import os
import unittest
-
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+from unittest import mock
import engineio
@@ -28,59 +23,72 @@ class TestWSGIApp(unittest.TestCase):
environ = {'PATH_INFO': '/engine.io/'}
start_response = "foo"
m(environ, start_response)
- mock_eio_app.handle_request.assert_called_once_with(environ,
- start_response)
+ mock_eio_app.handle_request.assert_called_once_with(
+ environ, start_response
+ )
def test_static_files(self):
root_dir = os.path.dirname(__file__)
- m = engineio.WSGIApp('foo', None, static_files={
- '/': root_dir + '/index.html',
- '/foo': {'content_type': 'text/plain',
- 'filename': root_dir + '/index.html'},
- '/static': root_dir,
- '/static/test/': root_dir + '/',
- })
+ m = engineio.WSGIApp(
+ 'foo',
+ None,
+ static_files={
+ '/': root_dir + '/index.html',
+ '/foo': {
+ 'content_type': 'text/plain',
+ 'filename': root_dir + '/index.html',
+ },
+ '/static': root_dir,
+ '/static/test/': root_dir + '/',
+ },
+ )
def check_path(path, status_code, content_type, body):
environ = {'PATH_INFO': path}
start_response = mock.MagicMock()
r = m(environ, start_response)
- self.assertEqual(r, [body.encode('utf-8')])
+ assert r == [body.encode('utf-8')]
start_response.assert_called_once_with(
- status_code, [('Content-Type', content_type)])
+ status_code, [('Content-Type', content_type)]
+ )
check_path('/', '200 OK', 'text/html', '<html></html>\n')
check_path('/foo', '200 OK', 'text/plain', '<html></html>\n')
- check_path('/static/index.html', '200 OK', 'text/html',
- '<html></html>\n')
- check_path('/static/foo.bar', '404 Not Found', 'text/plain',
- 'Not Found')
- check_path('/static/test/index.html', '200 OK', 'text/html',
- '<html></html>\n')
+ check_path(
+ '/static/index.html', '200 OK', 'text/html', '<html></html>\n'
+ )
+ check_path(
+ '/static/foo.bar', '404 Not Found', 'text/plain', 'Not Found'
+ )
+ check_path(
+ '/static/test/index.html', '200 OK', 'text/html', '<html></html>\n'
+ )
check_path('/static/test/', '200 OK', 'text/html', '<html></html>\n')
check_path('/bar/foo', '404 Not Found', 'text/plain', 'Not Found')
check_path('', '404 Not Found', 'text/plain', 'Not Found')
m.static_files[''] = 'index.html'
- check_path('/static/test/', '200 OK', 'text/html',
- '<html></html>\n')
+ check_path('/static/test/', '200 OK', 'text/html', '<html></html>\n')
m.static_files[''] = {'filename': 'index.html'}
- check_path('/static/test/', '200 OK', 'text/html',
- '<html></html>\n')
+ check_path('/static/test/', '200 OK', 'text/html', '<html></html>\n')
- m.static_files[''] = {'filename': 'index.html',
- 'content_type': 'image/gif'}
- check_path('/static/test/', '200 OK', 'image/gif',
- '<html></html>\n')
+ m.static_files[''] = {
+ 'filename': 'index.html',
+ 'content_type': 'image/gif',
+ }
+ check_path('/static/test/', '200 OK', 'image/gif', '<html></html>\n')
m.static_files[''] = {'filename': 'test.gif'}
- check_path('/static/test/', '404 Not Found', 'text/plain',
- 'Not Found')
+ check_path('/static/test/', '404 Not Found', 'text/plain', 'Not Found')
m.static_files = {}
- check_path('/static/test/index.html', '404 Not Found', 'text/plain',
- 'Not Found')
+ check_path(
+ '/static/test/index.html',
+ '404 Not Found',
+ 'text/plain',
+ 'Not Found',
+ )
def test_404(self):
mock_wsgi_app = None
@@ -89,9 +97,10 @@ class TestWSGIApp(unittest.TestCase):
environ = {'PATH_INFO': '/foo/bar'}
start_response = mock.MagicMock()
r = m(environ, start_response)
- self.assertEqual(r, [b'Not Found'])
+ assert r == [b'Not Found']
start_response.assert_called_once_with(
- "404 Not Found", [('Content-Type', 'text/plain')])
+ "404 Not Found", [('Content-Type', 'text/plain')]
+ )
def test_custom_eio_path(self):
mock_wsgi_app = None
@@ -101,26 +110,30 @@ class TestWSGIApp(unittest.TestCase):
environ = {'PATH_INFO': '/engine.io/'}
start_response = mock.MagicMock()
r = m(environ, start_response)
- self.assertEqual(r, [b'Not Found'])
+ assert r == [b'Not Found']
start_response.assert_called_once_with(
- "404 Not Found", [('Content-Type', 'text/plain')])
+ "404 Not Found", [('Content-Type', 'text/plain')]
+ )
environ = {'PATH_INFO': '/foo/'}
m(environ, start_response)
- mock_eio_app.handle_request.assert_called_once_with(environ,
- start_response)
+ mock_eio_app.handle_request.assert_called_once_with(
+ environ, start_response
+ )
def test_custom_eio_path_slashes(self):
mock_wsgi_app = None
mock_eio_app = mock.Mock()
mock_eio_app.handle_request = mock.MagicMock()
- m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app,
- engineio_path='/foo/')
+ m = engineio.WSGIApp(
+ mock_eio_app, mock_wsgi_app, engineio_path='/foo/'
+ )
environ = {'PATH_INFO': '/foo/'}
start_response = mock.MagicMock()
m(environ, start_response)
- mock_eio_app.handle_request.assert_called_once_with(environ,
- start_response)
+ mock_eio_app.handle_request.assert_called_once_with(
+ environ, start_response
+ )
def test_custom_eio_path_leading_slash(self):
mock_wsgi_app = None
@@ -130,8 +143,9 @@ class TestWSGIApp(unittest.TestCase):
environ = {'PATH_INFO': '/foo/'}
start_response = mock.MagicMock()
m(environ, start_response)
- mock_eio_app.handle_request.assert_called_once_with(environ,
- start_response)
+ mock_eio_app.handle_request.assert_called_once_with(
+ environ, start_response
+ )
def test_custom_eio_path_trailing_slash(self):
mock_wsgi_app = None
@@ -141,8 +155,9 @@ class TestWSGIApp(unittest.TestCase):
environ = {'PATH_INFO': '/foo/'}
start_response = mock.MagicMock()
m(environ, start_response)
- mock_eio_app.handle_request.assert_called_once_with(environ,
- start_response)
+ mock_eio_app.handle_request.assert_called_once_with(
+ environ, start_response
+ )
def test_gunicorn_socket(self):
mock_wsgi_app = None
@@ -151,12 +166,12 @@ class TestWSGIApp(unittest.TestCase):
environ = {'gunicorn.socket': 123, 'PATH_INFO': '/foo/bar'}
start_response = mock.MagicMock()
m(environ, start_response)
- self.assertIn('eventlet.input', environ)
- self.assertEqual(environ['eventlet.input'].get_socket(), 123)
+ assert 'eventlet.input' in environ
+ assert environ['eventlet.input'].get_socket() == 123
def test_legacy_middleware_class(self):
m = engineio.Middleware('eio', 'wsgi', 'eio_path')
- self.assertEqual(m.engineio_app, 'eio')
- self.assertEqual(m.wsgi_app, 'wsgi')
- self.assertEqual(m.static_files, {})
- self.assertEqual(m.engineio_path, 'eio_path')
+ assert m.engineio_app == 'eio'
+ assert m.wsgi_app == 'wsgi'
+ assert m.static_files == {}
+ assert m.engineio_path == 'eio_path'
diff --git a/debian/tests/common/test_packet.py b/debian/tests/common/test_packet.py
index 15d6305..9a9b84c 100644
--- a/debian/tests/common/test_packet.py
+++ b/debian/tests/common/test_packet.py
@@ -1,6 +1,6 @@
import unittest
-import six
+import pytest
from engineio import packet
@@ -8,96 +8,94 @@ from engineio import packet
class TestPacket(unittest.TestCase):
def test_encode_default_packet(self):
pkt = packet.Packet()
- self.assertEqual(pkt.packet_type, packet.NOOP)
- self.assertIsNone(pkt.data)
- self.assertFalse(pkt.binary)
- self.assertEqual(pkt.encode(), b'6')
+ assert pkt.packet_type == packet.NOOP
+ assert pkt.data is None
+ assert not pkt.binary
+ assert pkt.encode() == '6'
def test_decode_default_packet(self):
- pkt = packet.Packet(encoded_packet=b'6')
- self.assertTrue(pkt.encode(), b'6')
+ pkt = packet.Packet(encoded_packet='6')
+ assert pkt.encode() == '6'
def test_encode_text_packet(self):
- data = six.text_type('text')
+ data = 'text'
pkt = packet.Packet(packet.MESSAGE, data=data)
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, data)
- self.assertFalse(pkt.binary)
- self.assertEqual(pkt.encode(), b'4text')
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == data
+ assert not pkt.binary
+ assert pkt.encode() == '4text'
def test_decode_text_packet(self):
pkt = packet.Packet(encoded_packet=b'4text')
- self.assertEqual(pkt.encode(), b'4text')
+ assert pkt.encode() == b'4text'
def test_encode_binary_packet(self):
- pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03',
- binary=True)
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, b'\x01\x02\x03')
- self.assertTrue(pkt.binary)
- self.assertEqual(pkt.encode(), b'\x04\x01\x02\x03')
+ pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03')
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == b'\x01\x02\x03'
+ assert pkt.binary
+ assert pkt.encode() == b'\x01\x02\x03'
def test_encode_binary_bytearray_packet(self):
- pkt = packet.Packet(packet.MESSAGE, data=bytearray(b'\x01\x02\x03'),
- binary=True)
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, b'\x01\x02\x03')
- self.assertTrue(pkt.binary)
- self.assertEqual(pkt.encode(), b'\x04\x01\x02\x03')
+ pkt = packet.Packet(packet.MESSAGE, data=bytearray(b'\x01\x02\x03'))
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == b'\x01\x02\x03'
+ assert pkt.binary
+ assert pkt.encode() == b'\x01\x02\x03'
def test_encode_binary_b64_packet(self):
- pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03\x04',
- binary=True)
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, b'\x01\x02\x03\x04')
- self.assertTrue(pkt.binary)
- self.assertEqual(pkt.encode(b64=True), b'b4AQIDBA==')
-
- def test_encode_binary_packet_py3(self):
- pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03')
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, b'\x01\x02\x03')
- self.assertTrue(pkt.binary)
- self.assertEqual(pkt.encode(), b'\x04\x01\x02\x03')
+ pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03\x04')
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == b'\x01\x02\x03\x04'
+ assert pkt.binary
+ assert pkt.encode(b64=True) == 'bAQIDBA=='
def test_decode_binary_packet(self):
pkt = packet.Packet(encoded_packet=b'\x04\x01\x02\x03')
- self.assertTrue(pkt.encode(), b'\x04\x01\x02\x03')
+ assert pkt.encode(), b'\x04\x01\x02\x03'
def test_decode_binary_bytearray_packet(self):
pkt = packet.Packet(encoded_packet=bytearray(b'\x04\x01\x02\x03'))
- self.assertTrue(pkt.encode(), b'\x04\x01\x02\x03')
+ assert pkt.encode(), b'\x04\x01\x02\x03'
def test_decode_binary_b64_packet(self):
pkt = packet.Packet(encoded_packet=b'b4AAEC')
- self.assertTrue(pkt.encode(), b'\x04\x01\x02\x03')
+ assert pkt.encode(), b'\x04\x01\x02\x03'
def test_encode_json_packet(self):
pkt = packet.Packet(packet.MESSAGE, data={'a': 123, 'b': '456'})
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, {'a': 123, 'b': '456'})
- self.assertFalse(pkt.binary)
- self.assertIn(pkt.encode(), [b'4{"a":123,"b":"456"}',
- b'4{"b":"456","a":123}'])
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == {'a': 123, 'b': '456'}
+ assert not pkt.binary
+ assert pkt.encode() in [
+ '4{"a":123,"b":"456"}',
+ '4{"b":"456","a":123}',
+ ]
def test_decode_json_packet(self):
pkt = packet.Packet(encoded_packet=b'4{"a":123,"b":"456"}')
- self.assertIn(pkt.encode(), [b'4{"a":123,"b":"456"}',
- b'4{"b":"456","a":123}'])
+ assert pkt.encode() in [
+ b'4{"a":123,"b":"456"}',
+ b'4{"b":"456","a":123}',
+ ]
def test_encode_number_packet(self):
pkt = packet.Packet(packet.MESSAGE, data=123)
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
- self.assertEqual(pkt.data, 123)
- self.assertFalse(pkt.binary)
- self.assertEqual(pkt.encode(), b'4123')
+ assert pkt.packet_type == packet.MESSAGE
+ assert pkt.data == 123
+ assert not pkt.binary
+ assert pkt.encode() == '4123'
def test_decode_number_packet(self):
- pkt = packet.Packet(encoded_packet=b'4123')
- self.assertEqual(pkt.packet_type, packet.MESSAGE)
+ pkt = packet.Packet(encoded_packet='4123')
+ assert pkt.packet_type == packet.MESSAGE
# integer payloads are parsed as strings, see
# https://github.com/miguelgrinberg/python-engineio/issues/75
# for background on this decision
- self.assertEqual(pkt.data, '123')
- self.assertFalse(pkt.binary)
- self.assertEqual(pkt.encode(), b'4123')
+ assert pkt.data == '123'
+ assert not pkt.binary
+ assert pkt.encode() == '4123'
+
+ def test_binary_non_message_packet(self):
+ with pytest.raises(ValueError):
+ packet.Packet(packet.NOOP, b'\x01\x02\x03')
diff --git a/debian/tests/common/test_payload.py b/debian/tests/common/test_payload.py
index 20dad1b..06c3289 100644
--- a/debian/tests/common/test_payload.py
+++ b/debian/tests/common/test_payload.py
@@ -1,6 +1,6 @@
import unittest
-import six
+import pytest
from engineio import packet
from engineio import payload
@@ -9,88 +9,60 @@ from engineio import payload
class TestPayload(unittest.TestCase):
def test_encode_empty_payload(self):
p = payload.Payload()
- self.assertEqual(p.packets, [])
- self.assertEqual(p.encode(), b'')
+ assert p.packets == []
+ assert p.encode() == ''
def test_decode_empty_payload(self):
- p = payload.Payload(encoded_payload=b'')
- self.assertEqual(p.encode(), b'')
+ p = payload.Payload(encoded_payload='')
+ assert p.encode() == ''
- def test_encode_payload_xhr2(self):
- pkt = packet.Packet(packet.MESSAGE, data=six.text_type('abc'))
+ def test_encode_payload_text(self):
+ pkt = packet.Packet(packet.MESSAGE, data='abc')
p = payload.Payload([pkt])
- self.assertEqual(p.packets, [pkt])
- self.assertEqual(p.encode(), b'\x00\x04\xff4abc')
-
- def test_decode_payload_xhr2(self):
- p = payload.Payload(encoded_payload=b'\x00\x04\xff4abc')
- self.assertEqual(p.encode(), b'\x00\x04\xff4abc')
-
- def test_encode_payload_xhr_text(self):
- pkt = packet.Packet(packet.MESSAGE, data=six.text_type('abc'))
- p = payload.Payload([pkt])
- self.assertEqual(p.packets, [pkt])
- self.assertEqual(p.encode(b64=True), b'4:4abc')
-
- def test_decode_payload_xhr_text(self):
- p = payload.Payload(encoded_payload=b'4:4abc')
- self.assertEqual(p.encode(), b'\x00\x04\xff4abc')
-
- def test_encode_payload_xhr_binary(self):
- pkt = packet.Packet(packet.MESSAGE, data=b'\x00\x01\x02', binary=True)
+ assert p.packets == [pkt]
+ assert p.encode() == '4abc'
+
+ def test_encode_payload_text_multiple(self):
+ pkt = packet.Packet(packet.MESSAGE, data='abc')
+ pkt2 = packet.Packet(packet.MESSAGE, data='def')
+ p = payload.Payload([pkt, pkt2])
+ assert p.packets == [pkt, pkt2]
+ assert p.encode() == '4abc\x1e4def'
+
+ def test_encode_payload_binary(self):
+ pkt = packet.Packet(packet.MESSAGE, data=b'\x00\x01\x02')
p = payload.Payload([pkt])
- self.assertEqual(p.packets, [pkt])
- self.assertEqual(p.encode(b64=True), b'6:b4AAEC')
-
- def test_decode_payload_xhr_binary(self):
- p = payload.Payload(encoded_payload=b'6:b4AAEC')
- self.assertEqual(p.encode(), b'\x01\x04\xff\x04\x00\x01\x02')
+ assert p.packets == [pkt]
+ assert p.encode() == 'bAAEC'
+
+ def test_encode_payload_binary_multiple(self):
+ pkt = packet.Packet(packet.MESSAGE, data=b'\x00\x01\x02')
+ pkt2 = packet.Packet(packet.MESSAGE, data=b'\x03\x04\x05\x06')
+ p = payload.Payload([pkt, pkt2])
+ assert p.packets == [pkt, pkt2]
+ assert p.encode() == 'bAAEC\x1ebAwQFBg=='
+
+ def test_encode_payload_text_binary_multiple(self):
+ pkt = packet.Packet(packet.MESSAGE, data='abc')
+ pkt2 = packet.Packet(packet.MESSAGE, data=b'\x03\x04\x05\x06')
+ p = payload.Payload([pkt, pkt2, pkt2, pkt])
+ assert p.packets == [pkt, pkt2, pkt2, pkt]
+ assert p.encode() == '4abc\x1ebAwQFBg==\x1ebAwQFBg==\x1e4abc'
def test_encode_jsonp_payload(self):
- pkt = packet.Packet(packet.MESSAGE, data=six.text_type('abc'))
+ pkt = packet.Packet(packet.MESSAGE, data='abc')
p = payload.Payload([pkt])
- self.assertEqual(p.packets, [pkt])
- self.assertEqual(p.encode(jsonp_index=233),
- b'___eio[233]("\x00\x04\xff4abc");')
- self.assertEqual(p.encode(jsonp_index=233, b64=True),
- b'___eio[233]("4:4abc");')
+ assert p.packets == [pkt]
+ assert p.encode(jsonp_index=233) == '___eio[233]("4abc");'
def test_decode_jsonp_payload(self):
- p = payload.Payload(encoded_payload=b'd=4:4abc')
- self.assertEqual(p.encode(), b'\x00\x04\xff4abc')
+ p = payload.Payload(encoded_payload='d=4abc')
+ assert p.encode() == '4abc'
def test_decode_invalid_payload(self):
- self.assertRaises(ValueError, payload.Payload,
- encoded_payload=b'bad payload')
-
- # performance improvements in the payload parser assume packets in a
- # payload are either all binary or all text, so the following test does
- # no work anymore.
- #
- # def test_decode_multi_payload(self):
- # p = payload.Payload(encoded_payload=b'4:4abc\x00\x04\xff4def')
- # self.assertEqual(len(p.packets), 2)
- # self.assertEqual(p.packets[0].data, 'abc')
- # self.assertEqual(p.packets[1].data, 'def')
-
- def test_decode_multi_binary_payload(self):
- p = payload.Payload(
- encoded_payload=b'\x00\x04\xff4abc\x00\x04\xff4def')
- self.assertEqual(len(p.packets), 2)
- self.assertEqual(p.packets[0].data, 'abc')
- self.assertEqual(p.packets[1].data, 'def')
-
- def test_decode_multi_text_payload(self):
- p = payload.Payload(encoded_payload=b'4:4abc4:4def')
- self.assertEqual(len(p.packets), 2)
- self.assertEqual(p.packets[0].data, 'abc')
- self.assertEqual(p.packets[1].data, 'def')
-
- def test_decode_multi_binary_payload_with_too_many_packets(self):
- self.assertRaises(
- ValueError, payload.Payload,
- encoded_payload=b'\x00\x04\xff4abc\x00\x04\xff4def' * 9)
+ with pytest.raises(ValueError):
+ payload.Payload(encoded_payload='bad payload')
- def test_decode_multi_text_payload_with_too_many_packets(self):
- self.assertRaises(ValueError, payload.Payload,
- encoded_payload=b'4:4abc4:4def' * 9)
+ def test_decode_multi_payload_with_too_many_packets(self):
+ with pytest.raises(ValueError):
+ payload.Payload(encoded_payload='4abc\x1e4def\x1e' * 9 + '6')
diff --git a/debian/tests/common/test_server.py b/debian/tests/common/test_server.py
index 550e31c..9082100 100644
--- a/debian/tests/common/test_server.py
+++ b/debian/tests/common/test_server.py
@@ -1,24 +1,21 @@
import gzip
import importlib
+import io
import json
import logging
import sys
import time
import unittest
+from unittest import mock
import zlib
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+import pytest
from engineio import exceptions
from engineio import packet
from engineio import payload
from engineio import server
-
original_import_module = importlib.import_module
@@ -62,12 +59,16 @@ class TestServer(unittest.TestCase):
def test_is_asyncio_based(self):
s = server.Server()
- self.assertEqual(s.is_asyncio_based(), False)
+ assert not s.is_asyncio_based()
def test_async_modes(self):
s = server.Server()
- self.assertEqual(s.async_modes(), ['eventlet', 'gevent_uwsgi',
- 'gevent', 'threading'])
+ assert s.async_modes() == [
+ 'eventlet',
+ 'gevent_uwsgi',
+ 'gevent',
+ 'threading',
+ ]
def test_create(self):
kwargs = {
@@ -80,45 +81,47 @@ class TestServer(unittest.TestCase):
'cookie': 'foo',
'cors_allowed_origins': ['foo', 'bar', 'baz'],
'cors_credentials': False,
- 'async_handlers': False}
+ 'async_handlers': False,
+ }
s = server.Server(**kwargs)
- for arg in six.iterkeys(kwargs):
- self.assertEqual(getattr(s, arg), kwargs[arg])
- self.assertEqual(s.ping_interval_grace_period, 5)
+ for arg in kwargs.keys():
+ assert getattr(s, arg) == kwargs[arg]
+ assert s.ping_interval_grace_period == 0
def test_create_with_grace_period(self):
s = server.Server(ping_interval=(1, 2))
- self.assertEqual(s.ping_interval, 1)
- self.assertEqual(s.ping_interval_grace_period, 2)
+ assert s.ping_interval == 1
+ assert s.ping_interval_grace_period == 2
def test_create_ignores_kwargs(self):
server.Server(foo='bar') # this should not raise
def test_async_mode_threading(self):
s = server.Server(async_mode='threading')
- self.assertEqual(s.async_mode, 'threading')
+ assert s.async_mode == 'threading'
import threading
+
try:
import queue
except ImportError:
import Queue as queue
- self.assertEqual(s._async['thread'], threading.Thread)
- self.assertEqual(s._async['queue'], queue.Queue)
- self.assertEqual(s._async['websocket'], None)
+ assert s._async['thread'] == threading.Thread
+ assert s._async['queue'] == queue.Queue
+ assert s._async['websocket'] is None
def test_async_mode_eventlet(self):
s = server.Server(async_mode='eventlet')
- self.assertEqual(s.async_mode, 'eventlet')
+ assert s.async_mode == 'eventlet'
from eventlet.green import threading
from eventlet import queue
from engineio.async_drivers import eventlet as async_eventlet
- self.assertEqual(s._async['thread'], threading.Thread)
- self.assertEqual(s._async['queue'], queue.Queue)
- self.assertEqual(s._async['websocket'], async_eventlet.WebSocketWSGI)
+ assert s._async['thread'] == threading.Thread
+ assert s._async['queue'] == queue.Queue
+ assert s._async['websocket'] == async_eventlet.WebSocketWSGI
@mock.patch('importlib.import_module', side_effect=_mock_import)
def test_async_mode_gevent_uwsgi(self, import_module):
@@ -131,16 +134,15 @@ class TestServer(unittest.TestCase):
sys.modules['gevent.event'].Event = 'bar'
sys.modules['uwsgi'] = mock.MagicMock()
s = server.Server(async_mode='gevent_uwsgi')
- self.assertEqual(s.async_mode, 'gevent_uwsgi')
+ assert s.async_mode == 'gevent_uwsgi'
from engineio.async_drivers import gevent_uwsgi as async_gevent_uwsgi
- self.assertEqual(s._async['thread'], async_gevent_uwsgi.Thread)
- self.assertEqual(s._async['queue'], 'foo')
- self.assertEqual(s._async['queue_empty'], RuntimeError)
- self.assertEqual(s._async['event'], 'bar')
- self.assertEqual(s._async['websocket'],
- async_gevent_uwsgi.uWSGIWebSocket)
+ assert s._async['thread'] == async_gevent_uwsgi.Thread
+ assert s._async['queue'] == 'foo'
+ assert s._async['queue_empty'] == RuntimeError
+ assert s._async['event'] == 'bar'
+ assert s._async['websocket'] == async_gevent_uwsgi.uWSGIWebSocket
del sys.modules['gevent']
del sys.modules['gevent.queue']
del sys.modules['gevent.event']
@@ -157,8 +159,8 @@ class TestServer(unittest.TestCase):
sys.modules['gevent.event'] = mock.MagicMock()
sys.modules['gevent.event'].Event = 'bar'
sys.modules['uwsgi'] = None
- self.assertRaises(ValueError, server.Server,
- async_mode='gevent_uwsgi')
+ with pytest.raises(ValueError):
+ server.Server(async_mode='gevent_uwsgi')
del sys.modules['gevent']
del sys.modules['gevent.queue']
del sys.modules['gevent.event']
@@ -176,15 +178,15 @@ class TestServer(unittest.TestCase):
sys.modules['uwsgi'] = mock.MagicMock()
del sys.modules['uwsgi'].websocket_handshake
s = server.Server(async_mode='gevent_uwsgi')
- self.assertEqual(s.async_mode, 'gevent_uwsgi')
+ assert s.async_mode == 'gevent_uwsgi'
from engineio.async_drivers import gevent_uwsgi as async_gevent_uwsgi
- self.assertEqual(s._async['thread'], async_gevent_uwsgi.Thread)
- self.assertEqual(s._async['queue'], 'foo')
- self.assertEqual(s._async['queue_empty'], RuntimeError)
- self.assertEqual(s._async['event'], 'bar')
- self.assertEqual(s._async['websocket'], None)
+ assert s._async['thread'] == async_gevent_uwsgi.Thread
+ assert s._async['queue'] == 'foo'
+ assert s._async['queue_empty'] == RuntimeError
+ assert s._async['event'] == 'bar'
+ assert s._async['websocket'] is None
del sys.modules['gevent']
del sys.modules['gevent.queue']
del sys.modules['gevent.event']
@@ -202,15 +204,15 @@ class TestServer(unittest.TestCase):
sys.modules['gevent.event'].Event = 'bar'
sys.modules['geventwebsocket'] = 'geventwebsocket'
s = server.Server(async_mode='gevent')
- self.assertEqual(s.async_mode, 'gevent')
+ assert s.async_mode == 'gevent'
from engineio.async_drivers import gevent as async_gevent
- self.assertEqual(s._async['thread'], async_gevent.Thread)
- self.assertEqual(s._async['queue'], 'foo')
- self.assertEqual(s._async['queue_empty'], RuntimeError)
- self.assertEqual(s._async['event'], 'bar')
- self.assertEqual(s._async['websocket'], async_gevent.WebSocketWSGI)
+ assert s._async['thread'] == async_gevent.Thread
+ assert s._async['queue'] == 'foo'
+ assert s._async['queue_empty'] == RuntimeError
+ assert s._async['event'] == 'bar'
+ assert s._async['websocket'] == async_gevent.WebSocketWSGI
del sys.modules['gevent']
del sys.modules['gevent.queue']
del sys.modules['gevent.event']
@@ -228,15 +230,15 @@ class TestServer(unittest.TestCase):
sys.modules['gevent.event'].Event = 'bar'
sys.modules['geventwebsocket'] = None
s = server.Server(async_mode='gevent')
- self.assertEqual(s.async_mode, 'gevent')
+ assert s.async_mode == 'gevent'
from engineio.async_drivers import gevent as async_gevent
- self.assertEqual(s._async['thread'], async_gevent.Thread)
- self.assertEqual(s._async['queue'], 'foo')
- self.assertEqual(s._async['queue_empty'], RuntimeError)
- self.assertEqual(s._async['event'], 'bar')
- self.assertEqual(s._async['websocket'], None)
+ assert s._async['thread'] == async_gevent.Thread
+ assert s._async['queue'] == 'foo'
+ assert s._async['queue_empty'] == RuntimeError
+ assert s._async['event'] == 'bar'
+ assert s._async['websocket'] is None
del sys.modules['gevent']
del sys.modules['gevent.queue']
del sys.modules['gevent.event']
@@ -247,41 +249,45 @@ class TestServer(unittest.TestCase):
@mock.patch('importlib.import_module', side_effect=_mock_import)
def test_async_mode_aiohttp(self, import_module):
sys.modules['aiohttp'] = mock.MagicMock()
- self.assertRaises(ValueError, server.Server, async_mode='aiohttp')
+ with pytest.raises(ValueError):
+ server.Server(async_mode='aiohttp')
@mock.patch('importlib.import_module', side_effect=[ImportError])
def test_async_mode_invalid(self, import_module):
- self.assertRaises(ValueError, server.Server, async_mode='foo')
+ with pytest.raises(ValueError):
+ server.Server(async_mode='foo')
@mock.patch('importlib.import_module', side_effect=[_mock_async])
def test_async_mode_auto_eventlet(self, import_module):
s = server.Server()
- self.assertEqual(s.async_mode, 'eventlet')
+ assert s.async_mode == 'eventlet'
- @mock.patch('importlib.import_module', side_effect=[ImportError,
- _mock_async])
+ @mock.patch(
+ 'importlib.import_module', side_effect=[ImportError, _mock_async]
+ )
def test_async_mode_auto_gevent_uwsgi(self, import_module):
s = server.Server()
- self.assertEqual(s.async_mode, 'gevent_uwsgi')
+ assert s.async_mode == 'gevent_uwsgi'
- @mock.patch('importlib.import_module', side_effect=[ImportError,
- ImportError,
- _mock_async])
+ @mock.patch(
+ 'importlib.import_module',
+ side_effect=[ImportError, ImportError, _mock_async],
+ )
def test_async_mode_auto_gevent(self, import_module):
s = server.Server()
- self.assertEqual(s.async_mode, 'gevent')
+ assert s.async_mode == 'gevent'
- @mock.patch('importlib.import_module', side_effect=[ImportError,
- ImportError,
- ImportError,
- _mock_async])
+ @mock.patch(
+ 'importlib.import_module',
+ side_effect=[ImportError, ImportError, ImportError, _mock_async],
+ )
def test_async_mode_auto_threading(self, import_module):
s = server.Server()
- self.assertEqual(s.async_mode, 'threading')
+ assert s.async_mode == 'threading'
def test_generate_id(self):
s = server.Server()
- self.assertNotEqual(s._generate_id(), s._generate_id())
+ assert s.generate_id() != s.generate_id()
def test_on_event(self):
s = server.Server()
@@ -289,14 +295,16 @@ class TestServer(unittest.TestCase):
@s.on('connect')
def foo():
pass
+
s.on('disconnect', foo)
- self.assertEqual(s.handlers['connect'], foo)
- self.assertEqual(s.handlers['disconnect'], foo)
+ assert s.handlers['connect'] == foo
+ assert s.handlers['disconnect'] == foo
def test_on_event_invalid(self):
s = server.Server()
- self.assertRaises(ValueError, s.on, 'invalid')
+ with pytest.raises(ValueError):
+ s.on('invalid')
def test_trigger_event(self):
s = server.Server()
@@ -312,12 +320,12 @@ class TestServer(unittest.TestCase):
return 'bar'
r = s._trigger_event('connect', 1, 2, run_async=False)
- self.assertEqual(r, 3)
+ assert r == 3
r = s._trigger_event('message', 3, 4, run_async=True)
r.join()
- self.assertEqual(f['bar'], 7)
+ assert f['bar'] == 7
r = s._trigger_event('message', 5, 6)
- self.assertEqual(r, 'bar')
+ assert r == 'bar'
def test_trigger_event_error(self):
s = server.Server()
@@ -331,26 +339,26 @@ class TestServer(unittest.TestCase):
return 1 / 0
r = s._trigger_event('connect', 1, 2, run_async=False)
- self.assertEqual(r, False)
+ assert not r
r = s._trigger_event('message', 3, 4, run_async=False)
- self.assertEqual(r, None)
+ assert r is None
def test_session(self):
s = server.Server()
mock_socket = self._get_mock_socket()
s.sockets['foo'] = mock_socket
with s.session('foo') as session:
- self.assertEqual(session, {})
+ assert session == {}
session['username'] = 'bar'
- self.assertEqual(s.get_session('foo'), {'username': 'bar'})
+ assert s.get_session('foo') == {'username': 'bar'}
def test_close_one_socket(self):
s = server.Server()
mock_socket = self._get_mock_socket()
s.sockets['foo'] = mock_socket
s.disconnect('foo')
- self.assertEqual(mock_socket.close.call_count, 1)
- self.assertNotIn('foo', s.sockets)
+ assert mock_socket.close.call_count == 1
+ assert 'foo' not in s.sockets
def test_close_all_sockets(self):
s = server.Server()
@@ -359,22 +367,22 @@ class TestServer(unittest.TestCase):
mock_sockets[sid] = self._get_mock_socket()
s.sockets[sid] = mock_sockets[sid]
s.disconnect()
- for socket in six.itervalues(mock_sockets):
- self.assertEqual(socket.close.call_count, 1)
- self.assertEqual(s.sockets, {})
+ for socket in mock_sockets.values():
+ assert socket.close.call_count == 1
+ assert s.sockets == {}
def test_upgrades(self):
s = server.Server()
s.sockets['foo'] = self._get_mock_socket()
- self.assertEqual(s._upgrades('foo', 'polling'), ['websocket'])
- self.assertEqual(s._upgrades('foo', 'websocket'), [])
+ assert s._upgrades('foo', 'polling') == ['websocket']
+ assert s._upgrades('foo', 'websocket') == []
s.sockets['foo'].upgraded = True
- self.assertEqual(s._upgrades('foo', 'polling'), [])
- self.assertEqual(s._upgrades('foo', 'websocket'), [])
+ assert s._upgrades('foo', 'polling') == []
+ assert s._upgrades('foo', 'websocket') == []
s.allow_upgrades = False
s.sockets['foo'].upgraded = True
- self.assertEqual(s._upgrades('foo', 'polling'), [])
- self.assertEqual(s._upgrades('foo', 'websocket'), [])
+ assert s._upgrades('foo', 'polling') == []
+ assert s._upgrades('foo', 'websocket') == []
def test_transport(self):
s = server.Server()
@@ -382,160 +390,141 @@ class TestServer(unittest.TestCase):
s.sockets['foo'].upgraded = False
s.sockets['bar'] = self._get_mock_socket()
s.sockets['bar'].upgraded = True
- self.assertEqual(s.transport('foo'), 'polling')
- self.assertEqual(s.transport('bar'), 'websocket')
+ assert s.transport('foo') == 'polling'
+ assert s.transport('bar') == 'websocket'
def test_bad_session(self):
s = server.Server()
s.sockets['foo'] = 'client'
- self.assertRaises(KeyError, s._get_socket, 'bar')
+ with pytest.raises(KeyError):
+ s._get_socket('bar')
def test_closed_socket(self):
s = server.Server()
s.sockets['foo'] = self._get_mock_socket()
s.sockets['foo'].closed = True
- self.assertRaises(KeyError, s._get_socket, 'foo')
+ with pytest.raises(KeyError):
+ s._get_socket('foo')
def test_jsonp_with_bad_index(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=abc'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&j=abc'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
def test_jsonp_index(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=233'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&j=233'}
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
- self.assertTrue(r[0].startswith(b'___eio[233]("'))
- self.assertTrue(r[0].endswith(b'");'))
+ assert start_response.call_args[0][0] == '200 OK'
+ assert r[0].startswith(b'___eio[233]("')
+ assert r[0].endswith(b'");')
def test_connect(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- self.assertEqual(len(s.sockets), 1)
- self.assertEqual(start_response.call_count, 1)
- self.assertEqual(start_response.call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- start_response.call_args[0][1])
- self.assertEqual(len(r), 1)
- packets = payload.Payload(encoded_payload=r[0]).packets
- self.assertEqual(len(packets), 1)
- self.assertEqual(packets[0].packet_type, packet.OPEN)
- self.assertIn('upgrades', packets[0].data)
- self.assertEqual(packets[0].data['upgrades'], ['websocket'])
- self.assertIn('sid', packets[0].data)
+ assert len(s.sockets) == 1
+ assert start_response.call_count == 1
+ assert start_response.call_args[0][0] == '200 OK'
+ assert (
+ 'Content-Type',
+ 'text/plain; charset=UTF-8',
+ ) in start_response.call_args[0][1]
+ assert len(r) == 1
+ packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets
+ assert len(packets) == 1
+ assert packets[0].packet_type == packet.OPEN
+ assert 'upgrades' in packets[0].data
+ assert packets[0].data['upgrades'] == ['websocket']
+ assert 'sid' in packets[0].data
def test_connect_no_upgrades(self):
s = server.Server(allow_upgrades=False)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
- start_response = mock.MagicMock()
- r = s.handle_request(environ, start_response)
- packets = payload.Payload(encoded_payload=r[0]).packets
- self.assertEqual(packets[0].data['upgrades'], [])
-
- def test_connect_b64_with_1(self):
- s = server.Server(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'b64=1'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
- s.handle_request(environ, start_response)
- self.assertTrue(start_response.call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'text/plain; charset=UTF-8'),
- start_response.call_args[0][1])
- s.send('1', b'\x00\x01\x02', binary=True)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=1'}
- r = s.handle_request(environ, start_response)
- self.assertEqual(r[0], b'6:b4AAEC')
-
- def test_connect_b64_with_true(self):
- s = server.Server(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'b64=true'}
- start_response = mock.MagicMock()
- s.handle_request(environ, start_response)
- self.assertTrue(start_response.call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'text/plain; charset=UTF-8'),
- start_response.call_args[0][1])
- s.send('1', b'\x00\x01\x02', binary=True)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=true'}
r = s.handle_request(environ, start_response)
- self.assertEqual(r[0], b'6:b4AAEC')
+ packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets
+ assert packets[0].data['upgrades'] == []
- def test_connect_b64_with_0(self):
- s = server.Server(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'b64=0'}
- start_response = mock.MagicMock()
- s.handle_request(environ, start_response)
- self.assertTrue(start_response.call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- start_response.call_args[0][1])
- s.send('1', b'\x00\x01\x02', binary=True)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=0'}
- r = s.handle_request(environ, start_response)
- self.assertEqual(r[0], b'\x01\x04\xff\x04\x00\x01\x02')
-
- def test_connect_b64_with_false(self):
- s = server.Server(allow_upgrades=False)
- s._generate_id = mock.MagicMock(return_value='1')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'b64=false'}
+ def test_connect_bad_eio_version(self):
+ s = server.Server()
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=1'}
start_response = mock.MagicMock()
- s.handle_request(environ, start_response)
- self.assertTrue(start_response.call_args[0][0], '200 OK')
- self.assertIn(('Content-Type', 'application/octet-stream'),
- start_response.call_args[0][1])
- s.send('1', b'\x00\x01\x02', binary=True)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=1&b64=false'}
r = s.handle_request(environ, start_response)
- self.assertEqual(r[0], b'\x01\x04\xff\x04\x00\x01\x02')
+ assert start_response.call_args[0][0], '400 BAD REQUEST'
+ assert b'unsupported version' in r[0]
def test_connect_custom_ping_times(self):
s = server.Server(ping_timeout=123, ping_interval=456)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- packets = payload.Payload(encoded_payload=r[0]).packets
- self.assertEqual(packets[0].data['pingTimeout'], 123000)
- self.assertEqual(packets[0].data['pingInterval'], 456000)
+ packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets
+ assert packets[0].data['pingTimeout'] == 123000
+ assert packets[0].data['pingInterval'] == 456000
- @mock.patch('engineio.socket.Socket.poll',
- side_effect=exceptions.QueueEmpty)
+ @mock.patch(
+ 'engineio.socket.Socket.poll', side_effect=exceptions.QueueEmpty
+ )
def test_connect_bad_poll(self, poll):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
- @mock.patch('engineio.socket.Socket',
- return_value=mock.MagicMock(connected=False, closed=False))
+ @mock.patch(
+ 'engineio.socket.Socket',
+ return_value=mock.MagicMock(connected=False, closed=False),
+ )
def test_connect_transport_websocket(self, Socket):
s = server.Server()
- s._generate_id = mock.MagicMock(return_value='123')
- environ = {'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'transport=websocket'}
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&transport=websocket',
+ 'HTTP_UPGRADE': 'websocket',
+ }
+ start_response = mock.MagicMock()
+ # force socket to stay open, so that we can check it later
+ Socket().closed = False
+ s.handle_request(environ, start_response)
+ assert s.sockets['123'].send.call_args[0][0].packet_type == packet.OPEN
+
+ @mock.patch(
+ 'engineio.socket.Socket',
+ return_value=mock.MagicMock(connected=False, closed=False),
+ )
+ def test_http_upgrade_case_insensitive(self, Socket):
+ s = server.Server()
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&transport=websocket',
+ 'HTTP_UPGRADE': 'WebSocket',
+ }
start_response = mock.MagicMock()
# force socket to stay open, so that we can check it later
Socket().closed = False
s.handle_request(environ, start_response)
- self.assertEqual(s.sockets['123'].send.call_args[0][0].packet_type,
- packet.OPEN)
+ assert s.sockets['123'].send.call_args[0][0].packet_type == packet.OPEN
- @mock.patch('engineio.socket.Socket',
- return_value=mock.MagicMock(connected=False, closed=False))
+ @mock.patch(
+ 'engineio.socket.Socket',
+ return_value=mock.MagicMock(connected=False, closed=False),
+ )
def test_connect_transport_websocket_closed(self, Socket):
s = server.Server()
- s._generate_id = mock.MagicMock(return_value='123')
- environ = {'REQUEST_METHOD': 'GET',
- 'QUERY_STRING': 'transport=websocket'}
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&transport=websocket',
+ 'HTTP_UPGRADE': 'websocket',
+ }
start_response = mock.MagicMock()
def mock_handle(environ, start_response):
@@ -543,239 +532,275 @@ class TestServer(unittest.TestCase):
Socket().handle_get_request = mock_handle
s.handle_request(environ, start_response)
- self.assertNotIn('123', s.sockets)
+ assert '123' not in s.sockets
def test_connect_transport_invalid(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=foo'}
+ environ = {'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&transport=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
+
+ def test_connect_transport_websocket_without_upgrade(self):
+ s = server.Server()
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&transport=websocket',
+ }
+ start_response = mock.MagicMock()
+ s.handle_request(environ, start_response)
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
def test_connect_cors_headers(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
def test_connect_cors_allowed_origin(self):
s = server.Server(cors_allowed_origins=['a', 'b'])
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'b'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'b',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'b'), headers)
+ assert ('Access-Control-Allow-Origin', 'b') in headers
def test_connect_cors_not_allowed_origin(self):
s = server.Server(cors_allowed_origins=['a', 'b'])
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'c'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'c',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
headers = start_response.call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Origin', 'c'), headers)
- self.assertNotIn(('Access-Control-Allow-Origin', '*'), headers)
+ assert ('Access-Control-Allow-Origin', 'c') not in headers
+ assert ('Access-Control-Allow-Origin', '*') not in headers
def test_connect_cors_headers_all_origins(self):
s = server.Server(cors_allowed_origins='*')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'foo'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'foo',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'foo'), headers)
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Origin', 'foo') in headers
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
def test_connect_cors_headers_one_origin(self):
s = server.Server(cors_allowed_origins='a')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'a'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'a',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'a'), headers)
- self.assertIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Origin', 'a') in headers
+ assert ('Access-Control-Allow-Credentials', 'true') in headers
def test_connect_cors_headers_one_origin_not_allowed(self):
s = server.Server(cors_allowed_origins='a')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'b'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'b',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
headers = start_response.call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Origin', 'b'), headers)
- self.assertNotIn(('Access-Control-Allow-Origin', '*'), headers)
+ assert ('Access-Control-Allow-Origin', 'b') not in headers
+ assert ('Access-Control-Allow-Origin', '*') not in headers
def test_connect_cors_headers_default_origin(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo',
- 'HTTP_ORIGIN': 'http://foo'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'foo',
+ 'HTTP_ORIGIN': 'http://foo',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'http://foo'),
- headers)
+ assert ('Access-Control-Allow-Origin', 'http://foo') in headers
def test_connect_cors_headers_default_origin_proxy_server(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo',
- 'HTTP_ORIGIN': 'https://bar',
- 'HTTP_X_FORWARDED_PROTO': 'https, ftp',
- 'HTTP_X_FORWARDED_HOST': 'bar , baz'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'foo',
+ 'HTTP_ORIGIN': 'https://bar',
+ 'HTTP_X_FORWARDED_PROTO': 'https, ftp',
+ 'HTTP_X_FORWARDED_HOST': 'bar , baz',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Origin', 'https://bar'),
- headers)
+ assert ('Access-Control-Allow-Origin', 'https://bar') in headers
def test_connect_cors_no_credentials(self):
s = server.Server(cors_credentials=False)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertNotIn(('Access-Control-Allow-Credentials', 'true'), headers)
+ assert ('Access-Control-Allow-Credentials', 'true') not in headers
def test_cors_options(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Methods', 'OPTIONS, GET, POST'),
- headers)
+ assert (
+ 'Access-Control-Allow-Methods',
+ 'OPTIONS, GET, POST',
+ ) in headers
def test_cors_request_headers(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET',
- 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS': 'Foo, Bar'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS': 'Foo, Bar',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
- self.assertIn(('Access-Control-Allow-Headers', 'Foo, Bar'), headers)
+ assert ('Access-Control-Allow-Headers', 'Foo, Bar') in headers
def test_connect_cors_disabled(self):
s = server.Server(cors_allowed_origins=[])
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '',
- 'HTTP_ORIGIN': 'http://foo'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4',
+ 'HTTP_ORIGIN': 'http://foo',
+ }
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
headers = start_response.call_args[0][1]
for header in headers:
- self.assertFalse(header[0].startswith('Access-Control-'))
+ assert not header[0].startswith('Access-Control-')
def test_connect_cors_default_no_origin(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
headers = start_response.call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
def test_connect_cors_all_no_origin(self):
s = server.Server(cors_allowed_origins='*')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
headers = start_response.call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
def test_connect_cors_disabled_no_origin(self):
s = server.Server(cors_allowed_origins=[])
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
headers = start_response.call_args[0][1]
for header in headers:
- self.assertNotEqual(header[0], 'Access-Control-Allow-Origin')
+ assert header[0] != 'Access-Control-Allow-Origin'
def test_connect_event(self):
s = server.Server()
- s._generate_id = mock.MagicMock(return_value='123')
- mock_event = mock.MagicMock()
+ s.generate_id = mock.MagicMock(return_value='123')
+ mock_event = mock.MagicMock(return_value=None)
s.on('connect')(mock_event)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
mock_event.assert_called_once_with('123', environ)
- self.assertEqual(len(s.sockets), 1)
+ assert len(s.sockets) == 1
def test_connect_event_rejects(self):
s = server.Server()
- s._generate_id = mock.MagicMock(return_value='123')
+ s.generate_id = mock.MagicMock(return_value='123')
mock_event = mock.MagicMock(return_value=False)
s.on('connect')(mock_event)
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
- s.handle_request(environ, start_response)
- self.assertEqual(len(s.sockets), 0)
- self.assertEqual(start_response.call_args[0][0], '401 UNAUTHORIZED')
+ ret = s.handle_request(environ, start_response)
+ assert len(s.sockets) == 0
+ assert start_response.call_args[0][0] == '401 UNAUTHORIZED'
+ assert ret == [b'"Unauthorized"']
+
+ def test_connect_event_rejects_with_message(self):
+ s = server.Server()
+ s.generate_id = mock.MagicMock(return_value='123')
+ mock_event = mock.MagicMock(return_value='not allowed')
+ s.on('connect')(mock_event)
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
+ start_response = mock.MagicMock()
+ ret = s.handle_request(environ, start_response)
+ assert len(s.sockets) == 0
+ assert start_response.call_args[0][0] == '401 UNAUTHORIZED'
+ assert ret == [b'"not allowed"']
def test_method_not_found(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'PUT', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'PUT', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '405 METHOD NOT FOUND')
+ assert start_response.call_args[0][0] == '405 METHOD NOT FOUND'
def test_get_request_with_bad_sid(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
def test_post_request_with_bad_sid(self):
s = server.Server()
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
def test_send(self):
s = server.Server()
mock_socket = self._get_mock_socket()
s.sockets['foo'] = mock_socket
s.send('foo', 'hello')
- self.assertEqual(mock_socket.send.call_count, 1)
- self.assertEqual(mock_socket.send.call_args[0][0].packet_type,
- packet.MESSAGE)
- self.assertEqual(mock_socket.send.call_args[0][0].data, 'hello')
+ assert mock_socket.send.call_count == 1
+ assert mock_socket.send.call_args[0][0].packet_type == packet.MESSAGE
+ assert mock_socket.send.call_args[0][0].data == 'hello'
def test_send_unknown_socket(self):
s = server.Server()
@@ -785,27 +810,27 @@ class TestServer(unittest.TestCase):
def test_get_request(self):
s = server.Server()
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
- self.assertEqual(len(r), 1)
- packets = payload.Payload(encoded_payload=r[0]).packets
- self.assertEqual(len(packets), 1)
- self.assertEqual(packets[0].packet_type, packet.MESSAGE)
+ assert start_response.call_args[0][0] == '200 OK'
+ assert len(r) == 1
+ packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets
+ assert len(packets) == 1
+ assert packets[0].packet_type == packet.MESSAGE
def test_get_request_custom_response(self):
s = server.Server()
mock_socket = self._get_mock_socket()
mock_socket.handle_get_request = mock.MagicMock(side_effect=['resp'])
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
- self.assertEqual(s.handle_request(environ, start_response), 'resp')
+ assert s.handle_request(environ, start_response) == 'resp'
def test_get_request_closes_socket(self):
s = server.Server()
@@ -817,169 +842,214 @@ class TestServer(unittest.TestCase):
mock_socket.handle_get_request = mock_get_request
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
- self.assertEqual(s.handle_request(environ, start_response), 'resp')
- self.assertNotIn('foo', s.sockets)
+ assert s.handle_request(environ, start_response) == 'resp'
+ assert 'foo' not in s.sockets
def test_get_request_error(self):
s = server.Server()
mock_socket = self._get_mock_socket()
mock_socket.handle_get_request = mock.MagicMock(
- side_effect=[exceptions.QueueEmpty])
+ side_effect=[exceptions.QueueEmpty]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
- self.assertEqual(len(s.sockets), 0)
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
+ assert len(s.sockets) == 0
def test_post_request(self):
s = server.Server()
mock_socket = self._get_mock_socket()
mock_socket.handle_post_request = mock.MagicMock()
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '200 OK')
+ assert start_response.call_args[0][0] == '200 OK'
def test_post_request_error(self):
s = server.Server()
mock_socket = self._get_mock_socket()
mock_socket.handle_post_request = mock.MagicMock(
- side_effect=[exceptions.EngineIOError])
+ side_effect=[exceptions.EngineIOError]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'}
+ environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertEqual(start_response.call_args[0][0],
- '400 BAD REQUEST')
- self.assertNotIn('foo', s.sockets)
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
+ assert 'foo' not in s.sockets
@staticmethod
def _gzip_decompress(b):
- bytesio = six.BytesIO(b)
+ bytesio = io.BytesIO(b)
with gzip.GzipFile(fileobj=bytesio, mode='r') as gz:
return gz.read()
def test_gzip_compression(self):
s = server.Server(compression_threshold=0)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip,deflate'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- self.assertIn(('Content-Encoding', 'gzip'),
- start_response.call_args[0][1])
+ assert ('Content-Encoding', 'gzip') in start_response.call_args[0][1]
self._gzip_decompress(r[0])
def test_deflate_compression(self):
s = server.Server(compression_threshold=0)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
- self.assertIn(('Content-Encoding', 'deflate'),
- start_response.call_args[0][1])
+ assert ('Content-Encoding', 'deflate') in start_response.call_args[0][
+ 1
+ ]
zlib.decompress(r[0])
def test_gzip_compression_threshold(self):
s = server.Server(compression_threshold=1000)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
for header, value in start_response.call_args[0][1]:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress, r[0])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(r[0])
def test_compression_disabled(self):
s = server.Server(http_compression=False, compression_threshold=0)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'gzip'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'gzip',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
for header, value in start_response.call_args[0][1]:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress, r[0])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(r[0])
def test_compression_unknown(self):
s = server.Server(compression_threshold=0)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': 'rar'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': 'rar',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
for header, value in start_response.call_args[0][1]:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress, r[0])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(r[0])
def test_compression_no_encoding(self):
s = server.Server(compression_threshold=0)
mock_socket = self._get_mock_socket()
- mock_socket.handle_get_request = mock.MagicMock(return_value=[
- packet.Packet(packet.MESSAGE, data='hello')])
+ mock_socket.handle_get_request = mock.MagicMock(
+ return_value=[packet.Packet(packet.MESSAGE, data='hello')]
+ )
s.sockets['foo'] = mock_socket
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_ACCEPT_ENCODING': ''}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'EIO=4&sid=foo',
+ 'HTTP_ACCEPT_ENCODING': '',
+ }
start_response = mock.MagicMock()
r = s.handle_request(environ, start_response)
for header, value in start_response.call_args[0][1]:
- self.assertNotEqual(header, 'Content-Encoding')
- self.assertRaises(IOError, self._gzip_decompress, r[0])
+ assert header != 'Content-Encoding'
+ with pytest.raises(IOError):
+ self._gzip_decompress(r[0])
def test_cookie(self):
s = server.Server(cookie='sid')
- s._generate_id = mock.MagicMock(return_value='123')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
- self.assertIn(('Set-Cookie', 'sid=123'),
- start_response.call_args[0][1])
+ assert ('Set-Cookie', 'sid=123; path=/; SameSite=Lax') \
+ in start_response.call_args[0][1]
+
+ def test_cookie_dict(self):
+ def get_path():
+ return '/a'
+
+ s = server.Server(cookie={
+ 'name': 'test',
+ 'path': get_path,
+ 'SameSite': 'None',
+ 'Secure': True,
+ 'HttpOnly': True
+ })
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
+ start_response = mock.MagicMock()
+ s.handle_request(environ, start_response)
+ assert ('Set-Cookie', 'test=123; path=/a; SameSite=None; Secure; '
+ 'HttpOnly') in start_response.call_args[0][1]
def test_no_cookie(self):
s = server.Server(cookie=None)
- s._generate_id = mock.MagicMock(return_value='123')
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ s.generate_id = mock.MagicMock(return_value='123')
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
for header, value in start_response.call_args[0][1]:
- self.assertNotEqual(header, 'Set-Cookie')
+ assert header != 'Set-Cookie'
def test_logger(self):
s = server.Server(logger=False)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.ERROR)
+ assert s.logger.getEffectiveLevel() == logging.ERROR
s.logger.setLevel(logging.NOTSET)
s = server.Server(logger=True)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.INFO)
+ assert s.logger.getEffectiveLevel() == logging.INFO
s.logger.setLevel(logging.WARNING)
s = server.Server(logger=True)
- self.assertEqual(s.logger.getEffectiveLevel(), logging.WARNING)
+ assert s.logger.getEffectiveLevel() == logging.WARNING
s.logger.setLevel(logging.NOTSET)
my_logger = logging.Logger('foo')
s = server.Server(logger=my_logger)
- self.assertEqual(s.logger, my_logger)
+ assert s.logger == my_logger
def test_custom_json(self):
# Warning: this test cannot run in parallel with other tests, as it
@@ -996,9 +1066,9 @@ class TestServer(unittest.TestCase):
server.Server(json=CustomJSON)
pkt = packet.Packet(packet.MESSAGE, data={'foo': 'bar'})
- self.assertEqual(pkt.encode(), b'4*** encoded ***')
+ assert pkt.encode() == '4*** encoded ***'
pkt2 = packet.Packet(encoded_packet=pkt.encode())
- self.assertEqual(pkt2.data, '+++ decoded +++')
+ assert pkt2.data == '+++ decoded +++'
# restore the default JSON module
packet.Packet.json = json
@@ -1012,32 +1082,42 @@ class TestServer(unittest.TestCase):
s = server.Server()
task = s.start_background_task(bg_task)
task.join()
- self.assertIn('task', flag)
- self.assertTrue(flag['task'])
+ assert 'task' in flag
+ assert flag['task']
def test_sleep(self):
s = server.Server()
t = time.time()
s.sleep(0.1)
- self.assertTrue(time.time() - t > 0.1)
+ assert time.time() - t > 0.1
def test_create_queue(self):
s = server.Server()
q = s.create_queue()
empty = s.get_queue_empty_exception()
- self.assertRaises(empty, q.get, timeout=0.01)
+ with pytest.raises(empty):
+ q.get(timeout=0.01)
def test_create_event(self):
s = server.Server()
e = s.create_event()
- self.assertFalse(e.is_set())
+ assert not e.is_set()
e.set()
- self.assertTrue(e.is_set())
+ assert e.is_set()
+
+ def test_log_error_once(self):
+ s = server.Server(logger=mock.MagicMock())
+ s._log_error_once('foo', 'foo-key')
+ s._log_error_once('foo', 'foo-key')
+ s.logger.error.assert_called_with(
+ 'foo (further occurrences of this error will be logged with '
+ 'level INFO)')
+ s.logger.info.assert_called_with('foo')
def test_service_task_started(self):
s = server.Server(monitor_clients=True)
s._service_task = mock.MagicMock()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'}
start_response = mock.MagicMock()
s.handle_request(environ, start_response)
s._service_task.assert_called_once_with()
diff --git a/debian/tests/common/test_socket.py b/debian/tests/common/test_socket.py
index 543ff6d..33785e9 100644
--- a/debian/tests/common/test_socket.py
+++ b/debian/tests/common/test_socket.py
@@ -1,11 +1,9 @@
+import io
import time
import unittest
+from unittest import mock
-import six
-if six.PY3:
- from unittest import mock
-else:
- import mock
+import pytest
from engineio import exceptions
from engineio import packet
@@ -29,9 +27,12 @@ class TestSocket(unittest.TestCase):
except ImportError:
import Queue as queue
import threading
- mock_server._async = {'threading': threading.Thread,
- 'queue': queue.Queue,
- 'websocket': None}
+
+ mock_server._async = {
+ 'threading': threading.Thread,
+ 'queue': queue.Queue,
+ 'websocket': None,
+ }
def bg_task(target, *args, **kwargs):
th = threading.Thread(target=target, args=args, kwargs=kwargs)
@@ -54,19 +55,20 @@ class TestSocket(unittest.TestCase):
def test_create(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
- self.assertEqual(s.server, mock_server)
- self.assertEqual(s.sid, 'sid')
- self.assertFalse(s.upgraded)
- self.assertFalse(s.closed)
- self.assertTrue(hasattr(s.queue, 'get'))
- self.assertTrue(hasattr(s.queue, 'put'))
- self.assertTrue(hasattr(s.queue, 'task_done'))
- self.assertTrue(hasattr(s.queue, 'join'))
+ assert s.server == mock_server
+ assert s.sid == 'sid'
+ assert not s.upgraded
+ assert not s.closed
+ assert hasattr(s.queue, 'get')
+ assert hasattr(s.queue, 'put')
+ assert hasattr(s.queue, 'task_done')
+ assert hasattr(s.queue, 'join')
def test_empty_poll(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
- self.assertRaises(exceptions.QueueEmpty, s.poll)
+ with pytest.raises(exceptions.QueueEmpty):
+ s.poll()
def test_poll(self):
mock_server = self._get_mock_server()
@@ -75,38 +77,73 @@ class TestSocket(unittest.TestCase):
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
s.send(pkt1)
s.send(pkt2)
- self.assertEqual(s.poll(), [pkt1, pkt2])
+ assert s.poll() == [pkt1, pkt2]
- def test_ping_pong(self):
+ def test_poll_none(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
- s.receive(packet.Packet(packet.PING, data='abc'))
- r = s.poll()
- self.assertEqual(len(r), 1)
- self.assertTrue(r[0].encode(), b'3abc')
+ s.queue.put(None)
+ assert s.poll() == []
+
+ def test_poll_none_after_packet(self):
+ mock_server = self._get_mock_server()
+ s = socket.Socket(mock_server, 'sid')
+ pkt = packet.Packet(packet.MESSAGE, data='hello')
+ s.send(pkt)
+ s.queue.put(None)
+ assert s.poll() == [pkt]
+ assert s.poll() == []
+
+ def test_schedule_ping(self):
+ mock_server = self._get_mock_server()
+ mock_server.ping_interval = 0.01
+ s = socket.Socket(mock_server, 'sid')
+ s.send = mock.MagicMock()
+ s.schedule_ping()
+ time.sleep(0.05)
+ assert s.last_ping is not None
+ assert s.send.call_args_list[0][0][0].encode() == '2'
+
+ def test_schedule_ping_closed_socket(self):
+ mock_server = self._get_mock_server()
+ mock_server.ping_interval = 0.01
+ s = socket.Socket(mock_server, 'sid')
+ s.send = mock.MagicMock()
+ s.closed = True
+ s.schedule_ping()
+ time.sleep(0.05)
+ assert s.last_ping is None
+ s.send.assert_not_called()
+
+ def test_pong(self):
+ mock_server = self._get_mock_server()
+ s = socket.Socket(mock_server, 'sid')
+ s.schedule_ping = mock.MagicMock()
+ s.receive(packet.Packet(packet.PONG))
+ s.schedule_ping.assert_called_once_with()
def test_message_async_handler(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.receive(packet.Packet(packet.MESSAGE, data='foo'))
- mock_server._trigger_event.assert_called_once_with('message', 'sid',
- 'foo',
- run_async=True)
+ mock_server._trigger_event.assert_called_once_with(
+ 'message', 'sid', 'foo', run_async=True
+ )
def test_message_sync_handler(self):
mock_server = self._get_mock_server()
mock_server.async_handlers = False
s = socket.Socket(mock_server, 'sid')
s.receive(packet.Packet(packet.MESSAGE, data='foo'))
- mock_server._trigger_event.assert_called_once_with('message', 'sid',
- 'foo',
- run_async=False)
+ mock_server._trigger_event.assert_called_once_with(
+ 'message', 'sid', 'foo', run_async=False
+ )
def test_invalid_packet(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
- self.assertRaises(exceptions.UnknownPacketError, s.receive,
- packet.Packet(packet.OPEN))
+ with pytest.raises(exceptions.UnknownPacketError):
+ s.receive(packet.Packet(packet.OPEN))
def test_timeout(self):
mock_server = self._get_mock_server()
@@ -128,49 +165,60 @@ class TestSocket(unittest.TestCase):
environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
start_response = mock.MagicMock()
packets = s.handle_get_request(environ, start_response)
- self.assertEqual(packets, [pkt1, pkt2])
+ assert packets == [pkt1, pkt2]
def test_polling_read_error(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'foo')
environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'}
start_response = mock.MagicMock()
- self.assertRaises(exceptions.QueueEmpty, s.handle_get_request, environ,
- start_response)
+ with pytest.raises(exceptions.QueueEmpty):
+ s.handle_get_request(environ, start_response)
def test_polling_write(self):
mock_server = self._get_mock_server()
mock_server.max_http_buffer_size = 1000
pkt1 = packet.Packet(packet.MESSAGE, data='hello')
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
- p = payload.Payload(packets=[pkt1, pkt2]).encode()
+ p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8')
s = socket.Socket(mock_server, 'foo')
s.receive = mock.MagicMock()
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo',
- 'CONTENT_LENGTH': len(p), 'wsgi.input': six.BytesIO(p)}
+ environ = {
+ 'REQUEST_METHOD': 'POST',
+ 'QUERY_STRING': 'sid=foo',
+ 'CONTENT_LENGTH': len(p),
+ 'wsgi.input': io.BytesIO(p),
+ }
s.handle_post_request(environ)
- self.assertEqual(s.receive.call_count, 2)
+ assert s.receive.call_count == 2
def test_polling_write_too_large(self):
mock_server = self._get_mock_server()
pkt1 = packet.Packet(packet.MESSAGE, data='hello')
pkt2 = packet.Packet(packet.MESSAGE, data='bye')
- p = payload.Payload(packets=[pkt1, pkt2]).encode()
+ p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8')
mock_server.max_http_buffer_size = len(p) - 1
s = socket.Socket(mock_server, 'foo')
s.receive = mock.MagicMock()
- environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo',
- 'CONTENT_LENGTH': len(p), 'wsgi.input': six.BytesIO(p)}
- self.assertRaises(exceptions.ContentTooLongError,
- s.handle_post_request, environ)
+ environ = {
+ 'REQUEST_METHOD': 'POST',
+ 'QUERY_STRING': 'sid=foo',
+ 'CONTENT_LENGTH': len(p),
+ 'wsgi.input': io.BytesIO(p),
+ }
+ with pytest.raises(exceptions.ContentTooLongError):
+ s.handle_post_request(environ)
def test_upgrade_handshake(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'foo')
s._upgrade_websocket = mock.MagicMock()
- environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo',
- 'HTTP_CONNECTION': 'Foo,Upgrade,Bar',
- 'HTTP_UPGRADE': 'websocket'}
+ environ = {
+ 'REQUEST_METHOD': 'GET',
+ 'QUERY_STRING': 'sid=foo',
+ 'HTTP_CONNECTION': 'Foo,Upgrade,Bar',
+ 'HTTP_UPGRADE': 'websocket',
+ }
start_response = mock.MagicMock()
s.handle_get_request(environ, start_response)
s._upgrade_websocket.assert_called_once_with(environ, start_response)
@@ -186,7 +234,8 @@ class TestSocket(unittest.TestCase):
start_response = "bar"
s._upgrade_websocket(environ, start_response)
mock_server._async['websocket'].assert_called_once_with(
- s._websocket_handler)
+ s._websocket_handler
+ )
mock_ws.assert_called_once_with(environ, start_response)
def test_upgrade_twice(self):
@@ -197,8 +246,8 @@ class TestSocket(unittest.TestCase):
s.upgraded = True
environ = "foo"
start_response = "bar"
- self.assertRaises(IOError, s._upgrade_websocket,
- environ, start_response)
+ with pytest.raises(IOError):
+ s._upgrade_websocket(environ, start_response)
def test_upgrade_packet(self):
mock_server = self._get_mock_server()
@@ -206,18 +255,17 @@ class TestSocket(unittest.TestCase):
s.connected = True
s.receive(packet.Packet(packet.UPGRADE))
r = s.poll()
- self.assertEqual(len(r), 1)
- self.assertEqual(r[0].encode(), packet.Packet(packet.NOOP).encode())
+ assert len(r) == 1
+ assert r[0].encode() == packet.Packet(packet.NOOP).encode()
def test_upgrade_no_probe(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.connected = True
ws = mock.MagicMock()
- ws.wait.return_value = packet.Packet(packet.NOOP).encode(
- always_bytes=False)
+ ws.wait.return_value = packet.Packet(packet.NOOP).encode()
s._websocket_handler(ws)
- self.assertFalse(s.upgraded)
+ assert not s.upgraded
def test_upgrade_no_upgrade_packet(self):
mock_server = self._get_mock_server()
@@ -225,16 +273,17 @@ class TestSocket(unittest.TestCase):
s.connected = True
s.queue.join = mock.MagicMock(return_value=None)
ws = mock.MagicMock()
- probe = six.text_type('probe')
+ probe = 'probe'
ws.wait.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.NOOP).encode(always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.NOOP).encode(),
+ ]
s._websocket_handler(ws)
- ws.send.assert_called_once_with(packet.Packet(
- packet.PONG, data=probe).encode(always_bytes=False))
- self.assertEqual(s.queue.get().packet_type, packet.NOOP)
- self.assertFalse(s.upgraded)
+ ws.send.assert_called_once_with(
+ packet.Packet(packet.PONG, data=probe).encode()
+ )
+ assert s.queue.get().packet_type == packet.NOOP
+ assert not s.upgraded
def test_close_packet(self):
mock_server = self._get_mock_server()
@@ -248,7 +297,8 @@ class TestSocket(unittest.TestCase):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
pkt = packet.Packet(packet_type=99)
- self.assertRaises(exceptions.UnknownPacketError, s.receive, pkt)
+ with pytest.raises(exceptions.UnknownPacketError):
+ s.receive(pkt)
def test_upgrade_not_supported(self):
mock_server = self._get_mock_server()
@@ -265,23 +315,30 @@ class TestSocket(unittest.TestCase):
s = socket.Socket(mock_server, 'sid')
s.connected = False
s.queue.join = mock.MagicMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = mock.MagicMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = mock.MagicMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertTrue(s.connected)
- self.assertTrue(s.upgraded)
- self.assertEqual(mock_server._trigger_event.call_count, 2)
- mock_server._trigger_event.assert_has_calls([
- mock.call('message', 'sid', 'foo', run_async=True),
- mock.call('disconnect', 'sid', run_async=False)])
+ assert s.connected
+ assert s.upgraded
+ assert mock_server._trigger_event.call_count == 2
+ mock_server._trigger_event.assert_has_calls(
+ [
+ mock.call('message', 'sid', 'foo', run_async=True),
+ mock.call('disconnect', 'sid', run_async=False),
+ ]
+ )
ws.send.assert_called_with('4bar')
def test_websocket_upgrade_read_write(self):
@@ -289,26 +346,32 @@ class TestSocket(unittest.TestCase):
s = socket.Socket(mock_server, 'sid')
s.connected = True
s.queue.join = mock.MagicMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- probe = six.text_type('probe')
- s.poll = mock.MagicMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ probe = 'probe'
+ s.poll = mock.MagicMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE).encode(always_bytes=False),
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE).encode(),
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertTrue(s.upgraded)
- self.assertEqual(mock_server._trigger_event.call_count, 2)
- mock_server._trigger_event.assert_has_calls([
- mock.call('message', 'sid', 'foo', run_async=True),
- mock.call('disconnect', 'sid', run_async=False)])
+ assert s.upgraded
+ assert mock_server._trigger_event.call_count == 2
+ mock_server._trigger_event.assert_has_calls(
+ [
+ mock.call('message', 'sid', 'foo', run_async=True),
+ mock.call('disconnect', 'sid', run_async=False),
+ ]
+ )
ws.send.assert_called_with('4bar')
def test_websocket_upgrade_with_payload(self):
@@ -316,100 +379,120 @@ class TestSocket(unittest.TestCase):
s = socket.Socket(mock_server, 'sid')
s.connected = True
s.queue.join = mock.MagicMock(return_value=None)
- probe = six.text_type('probe')
+ probe = 'probe'
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE, data=b'2').encode(
- always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE, data='2').encode(),
+ ]
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertTrue(s.upgraded)
+ assert s.upgraded
def test_websocket_upgrade_with_backlog(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.connected = True
s.queue.join = mock.MagicMock(return_value=None)
- probe = six.text_type('probe')
- foo = six.text_type('foo')
+ probe = 'probe'
+ foo = 'foo'
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.PING, data=probe).encode(
- always_bytes=False),
- packet.Packet(packet.UPGRADE, data=b'2').encode(
- always_bytes=False)]
+ packet.Packet(packet.PING, data=probe).encode(),
+ packet.Packet(packet.UPGRADE, data='2').encode(),
+ ]
s.upgrading = True
s.send(packet.Packet(packet.MESSAGE, data=foo))
+ environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=sid'}
+ start_response = mock.MagicMock()
+ packets = s.handle_get_request(environ, start_response)
+ assert len(packets) == 1
+ assert packets[0].encode() == '6'
+ packets = s.poll()
+ assert len(packets) == 1
+ assert packets[0].encode() == '4foo'
+
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertTrue(s.upgraded)
- self.assertFalse(s.upgrading)
- self.assertEqual(s.packet_backlog, [])
- ws.send.assert_called_with('4foo')
+ assert s.upgraded
+ assert not s.upgrading
+ packets = s.handle_get_request(environ, start_response)
+ assert len(packets) == 1
+ assert packets[0].encode() == '6'
def test_websocket_read_write_wait_fail(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.connected = False
s.queue.join = mock.MagicMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = mock.MagicMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)],
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = mock.MagicMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- RuntimeError]
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ RuntimeError,
+ ]
ws.send.side_effect = [None, RuntimeError]
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertEqual(s.closed, True)
+ assert s.closed
def test_websocket_ignore_invalid_packet(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.connected = False
s.queue.join = mock.MagicMock(return_value=None)
- foo = six.text_type('foo')
- bar = six.text_type('bar')
- s.poll = mock.MagicMock(side_effect=[
- [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty])
+ foo = 'foo'
+ bar = 'bar'
+ s.poll = mock.MagicMock(
+ side_effect=[
+ [packet.Packet(packet.MESSAGE, data=bar)],
+ exceptions.QueueEmpty,
+ ]
+ )
ws = mock.MagicMock()
ws.wait.side_effect = [
- packet.Packet(packet.OPEN).encode(always_bytes=False),
- packet.Packet(packet.MESSAGE, data=foo).encode(
- always_bytes=False),
- None]
+ packet.Packet(packet.OPEN).encode(),
+ packet.Packet(packet.MESSAGE, data=foo).encode(),
+ None,
+ ]
s._websocket_handler(ws)
self._join_bg_tasks()
- self.assertTrue(s.connected)
- self.assertEqual(mock_server._trigger_event.call_count, 2)
- mock_server._trigger_event.assert_has_calls([
- mock.call('message', 'sid', foo, run_async=True),
- mock.call('disconnect', 'sid', run_async=False)])
+ assert s.connected
+ assert mock_server._trigger_event.call_count == 2
+ mock_server._trigger_event.assert_has_calls(
+ [
+ mock.call('message', 'sid', foo, run_async=True),
+ mock.call('disconnect', 'sid', run_async=False),
+ ]
+ )
ws.send.assert_called_with('4bar')
def test_send_after_close(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.close(wait=False)
- self.assertRaises(exceptions.SocketIsClosedError, s.send,
- packet.Packet(packet.NOOP))
+ with pytest.raises(exceptions.SocketIsClosedError):
+ s.send(packet.Packet(packet.NOOP))
def test_close_after_close(self):
mock_server = self._get_mock_server()
s = socket.Socket(mock_server, 'sid')
s.close(wait=False)
- self.assertTrue(s.closed)
- self.assertEqual(mock_server._trigger_event.call_count, 1)
- mock_server._trigger_event.assert_called_once_with('disconnect', 'sid',
- run_async=False)
+ assert s.closed
+ assert mock_server._trigger_event.call_count == 1
+ mock_server._trigger_event.assert_called_once_with(
+ 'disconnect', 'sid', run_async=False
+ )
s.close()
- self.assertEqual(mock_server._trigger_event.call_count, 1)
+ assert mock_server._trigger_event.call_count == 1
def test_close_and_wait(self):
mock_server = self._get_mock_server()
@@ -423,4 +506,4 @@ class TestSocket(unittest.TestCase):
s = socket.Socket(mock_server, 'sid')
s.queue = mock.MagicMock()
s.close(wait=False)
- self.assertEqual(s.queue.join.call_count, 0)
+ assert s.queue.join.call_count == 0
diff --git a/engineio/__init__.py b/engineio/__init__.py
index 76b5574..eb9f5fe 100644
--- a/engineio/__init__.py
+++ b/engineio/__init__.py
@@ -17,7 +17,7 @@ else: # pragma: no cover
get_tornado_handler = None
ASGIApp = None
-__version__ = '3.11.1'
+__version__ = '4.0.0'
__all__ = ['__version__', 'Server', 'WSGIApp', 'Middleware', 'Client']
if AsyncServer is not None: # pragma: no cover
diff --git a/engineio/async_drivers/aiohttp.py b/engineio/async_drivers/aiohttp.py
index ad69876..a591995 100644
--- a/engineio/async_drivers/aiohttp.py
+++ b/engineio/async_drivers/aiohttp.py
@@ -3,7 +3,6 @@ import sys
from urllib.parse import urlsplit
from aiohttp.web import Response, WebSocketResponse
-import six
def create_route(app, engineio_server, engineio_endpoint):
@@ -113,8 +112,8 @@ class WebSocket(object): # pragma: no cover
async def wait(self):
msg = await self._sock.receive()
- if not isinstance(msg.data, six.binary_type) and \
- not isinstance(msg.data, six.text_type):
+ if not isinstance(msg.data, bytes) and \
+ not isinstance(msg.data, str):
raise IOError()
return msg.data
diff --git a/engineio/async_drivers/asgi.py b/engineio/async_drivers/asgi.py
index 9f14ef0..fe94e59 100644
--- a/engineio/async_drivers/asgi.py
+++ b/engineio/async_drivers/asgi.py
@@ -1,5 +1,6 @@
import os
import sys
+import asyncio
from engineio.static_files import get_static_file
@@ -19,6 +20,10 @@ class ASGIApp:
:param engineio_path: The endpoint where the Engine.IO application should
be installed. The default value is appropriate for
most cases.
+ :param on_startup: function to be called on application startup; can be
+ coroutine
+ :param on_shutdown: function to be called on application shutdown; can be
+ coroutine
Example usage::
@@ -34,11 +39,14 @@ class ASGIApp:
uvicorn.run(app, '127.0.0.1', 5000)
"""
def __init__(self, engineio_server, other_asgi_app=None,
- static_files=None, engineio_path='engine.io'):
+ static_files=None, engineio_path='engine.io',
+ on_startup=None, on_shutdown=None):
self.engineio_server = engineio_server
self.other_asgi_app = other_asgi_app
self.engineio_path = engineio_path.strip('/')
self.static_files = static_files or {}
+ self.on_startup = on_startup
+ self.on_shutdown = on_shutdown
async def __call__(self, scope, receive, send):
if scope['type'] in ['http', 'websocket'] and \
@@ -73,11 +81,29 @@ class ASGIApp:
await self.not_found(receive, send)
async def lifespan(self, receive, send):
- event = await receive()
- if event['type'] == 'lifespan.startup':
- await send({'type': 'lifespan.startup.complete'})
- elif event['type'] == 'lifespan.shutdown':
- await send({'type': 'lifespan.shutdown.complete'})
+ while True:
+ event = await receive()
+ if event['type'] == 'lifespan.startup':
+ if self.on_startup:
+ try:
+ await self.on_startup() \
+ if asyncio.iscoroutinefunction(self.on_startup) \
+ else self.on_startup()
+ except:
+ await send({'type': 'lifespan.startup.failed'})
+ return
+ await send({'type': 'lifespan.startup.complete'})
+ elif event['type'] == 'lifespan.shutdown':
+ if self.on_shutdown:
+ try:
+ await self.on_shutdown() \
+ if asyncio.iscoroutinefunction(self.on_shutdown) \
+ else self.on_shutdown()
+ except:
+ await send({'type': 'lifespan.shutdown.failed'})
+ return
+ await send({'type': 'lifespan.shutdown.complete'})
+ return
async def not_found(self, receive, send):
"""Return a 404 Not Found error to the client."""
@@ -111,7 +137,7 @@ async def translate_request(scope, receive, send):
if event['type'] == 'http.request':
payload += event.get('body') or b''
elif event['type'] == 'websocket.connect':
- await send({'type': 'websocket.accept'})
+ pass
else:
return {}
@@ -139,6 +165,7 @@ async def translate_request(scope, receive, send):
'SERVER_PORT': '0',
'asgi.receive': receive,
'asgi.send': send,
+ 'asgi.scope': scope,
}
for hdr_name, hdr_value in scope['headers']:
@@ -163,6 +190,14 @@ async def translate_request(scope, receive, send):
async def make_response(status, headers, payload, environ):
headers = [(h[0].encode('utf-8'), h[1].encode('utf-8')) for h in headers]
+ if 'HTTP_SEC_WEBSOCKET_VERSION' in environ:
+ if status.startswith('200 '):
+ await environ['asgi.send']({'type': 'websocket.accept',
+ 'headers': headers})
+ else:
+ await environ['asgi.send']({'type': 'websocket.close'})
+ return
+
await environ['asgi.send']({'type': 'http.response.start',
'status': int(status.split(' ')[0]),
'headers': headers})
@@ -183,6 +218,7 @@ class WebSocket(object): # pragma: no cover
async def __call__(self, environ):
self.asgi_receive = environ['asgi.receive']
self.asgi_send = environ['asgi.send']
+ await self.asgi_send({'type': 'websocket.accept'})
await self.handler(self)
async def close(self):
diff --git a/engineio/async_drivers/gevent_uwsgi.py b/engineio/async_drivers/gevent_uwsgi.py
index 07fa2a7..bdee812 100644
--- a/engineio/async_drivers/gevent_uwsgi.py
+++ b/engineio/async_drivers/gevent_uwsgi.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import
-import six
-
import gevent
from gevent import queue
from gevent.event import Event
@@ -75,7 +73,7 @@ class uWSGIWebSocket(object): # pragma: no cover
def _send(self, msg):
"""Transmits message either in binary or UTF-8 text mode,
depending on its type."""
- if isinstance(msg, six.binary_type):
+ if isinstance(msg, bytes):
method = uwsgi.websocket_send_binary
else:
method = uwsgi.websocket_send
@@ -86,11 +84,11 @@ class uWSGIWebSocket(object): # pragma: no cover
def _decode_received(self, msg):
"""Returns either bytes or str, depending on message type."""
- if not isinstance(msg, six.binary_type):
+ if not isinstance(msg, bytes):
# already decoded - do nothing
return msg
# only decode from utf-8 if message is not binary data
- type = six.byte2int(msg[0:1])
+ type = ord(msg[0:1])
if type >= 48: # no binary
return msg.decode('utf-8')
# binary message, don't try to decode
diff --git a/engineio/async_drivers/sanic.py b/engineio/async_drivers/sanic.py
index 6929654..2400224 100644
--- a/engineio/async_drivers/sanic.py
+++ b/engineio/async_drivers/sanic.py
@@ -1,16 +1,15 @@
import sys
from urllib.parse import urlsplit
-from sanic.response import HTTPResponse
-try:
+try: # pragma: no cover
+ from sanic.response import HTTPResponse
from sanic.websocket import WebSocketProtocol
except ImportError:
- # the installed version of sanic does not have websocket support
+ HTTPResponse = None
WebSocketProtocol = None
-import six
-def create_route(app, engineio_server, engineio_endpoint):
+def create_route(app, engineio_server, engineio_endpoint): # pragma: no cover
"""This function sets up the engine.io endpoint as a route for the
application.
@@ -26,7 +25,7 @@ def create_route(app, engineio_server, engineio_endpoint):
pass
-def translate_request(request):
+def translate_request(request): # pragma: no cover
"""This function takes the arguments passed to the request handler and
uses them to generate a WSGI compatible environ dictionary.
"""
@@ -89,7 +88,7 @@ def translate_request(request):
return environ
-def make_response(status, headers, payload, environ):
+def make_response(status, headers, payload, environ): # pragma: no cover
"""This function generates an appropriate response object for this async
mode.
"""
@@ -129,8 +128,8 @@ class WebSocket(object): # pragma: no cover
async def wait(self):
data = await self._sock.recv()
- if not isinstance(data, six.binary_type) and \
- not isinstance(data, six.text_type):
+ if not isinstance(data, bytes) and \
+ not isinstance(data, str):
raise IOError()
return data
diff --git a/engineio/async_drivers/tornado.py b/engineio/async_drivers/tornado.py
index adfe18f..eb1c4de 100644
--- a/engineio/async_drivers/tornado.py
+++ b/engineio/async_drivers/tornado.py
@@ -5,15 +5,13 @@ from .. import exceptions
import tornado.web
import tornado.websocket
-import six
def get_tornado_handler(engineio_server):
class Handler(tornado.websocket.WebSocketHandler): # pragma: no cover
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- if isinstance(engineio_server.cors_allowed_origins,
- six.string_types):
+ if isinstance(engineio_server.cors_allowed_origins, str):
if engineio_server.cors_allowed_origins == '*':
self.allowed_origins = None
else:
@@ -170,8 +168,8 @@ class WebSocket(object): # pragma: no cover
async def wait(self):
msg = await self.tornado_handler.get_next_message()
- if not isinstance(msg, six.binary_type) and \
- not isinstance(msg, six.text_type):
+ if not isinstance(msg, bytes) and \
+ not isinstance(msg, str):
raise IOError()
return msg
diff --git a/engineio/asyncio_client.py b/engineio/asyncio_client.py
index 5445a9b..57691d6 100644
--- a/engineio/asyncio_client.py
+++ b/engineio/asyncio_client.py
@@ -1,17 +1,36 @@
import asyncio
+import signal
import ssl
+import threading
try:
import aiohttp
except ImportError: # pragma: no cover
aiohttp = None
-import six
from . import client
from . import exceptions
from . import packet
from . import payload
+async_signal_handler_set = False
+
+
+def async_signal_handler():
+ """SIGINT handler.
+
+ Disconnect all active async clients.
+ """
+ async def _handler():
+ asyncio.get_event_loop().stop()
+ for c in client.connected_clients[:]:
+ if c.is_asyncio_based():
+ await c.disconnect()
+ else: # pragma: no cover
+ pass
+
+ asyncio.ensure_future(_handler())
+
class AsyncClient(client.Client):
"""An Engine.IO client for asyncio.
@@ -22,13 +41,18 @@ class AsyncClient(client.Client):
:param logger: To enable logging set to ``True`` or pass a logger object to
use. To disable logging set to ``False``. The default is
- ``False``.
+ ``False``. Note that fatal errors are logged even when
+ ``logger`` is ``False``.
:param json: An alternative json module to use for encoding and decoding
packets. Custom json modules must have ``dumps`` and ``loads``
functions that are compatible with the standard library
versions.
:param request_timeout: A timeout in seconds for requests. The default is
5 seconds.
+ :param http_session: an initialized ``aiohttp.ClientSession`` object to be
+ used when sending requests to the server. Use it if
+ you need to add special client options such as proxy
+ servers, SSL certificates, etc.
:param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to
skip SSL certificate verification, allowing
connections to servers with self signed certificates.
@@ -37,7 +61,7 @@ class AsyncClient(client.Client):
def is_asyncio_based(self):
return True
- async def connect(self, url, headers={}, transports=None,
+ async def connect(self, url, headers=None, transports=None,
engineio_path='engine.io'):
"""Connect to an Engine.IO server.
@@ -60,11 +84,22 @@ class AsyncClient(client.Client):
eio = engineio.Client()
await eio.connect('http://localhost:5000')
"""
+ global async_signal_handler_set
+ if not async_signal_handler_set and \
+ threading.current_thread() == threading.main_thread():
+
+ try:
+ asyncio.get_event_loop().add_signal_handler(
+ signal.SIGINT, async_signal_handler)
+ async_signal_handler_set = True
+ except NotImplementedError: # pragma: no cover
+ self.logger.warning('Signal handler is unsupported')
+
if self.state != 'disconnected':
raise ValueError('Client is not in a disconnected state')
valid_transports = ['polling', 'websocket']
if transports is not None:
- if isinstance(transports, six.text_type):
+ if isinstance(transports, str):
transports = [transports]
transports = [transport for transport in transports
if transport in valid_transports]
@@ -73,7 +108,7 @@ class AsyncClient(client.Client):
self.transports = transports or valid_transports
self.queue = self.create_queue()
return await getattr(self, '_connect_' + self.transports[0])(
- url, headers, engineio_path)
+ url, headers or {}, engineio_path)
async def wait(self):
"""Wait until the connection with the server ends.
@@ -86,21 +121,16 @@ class AsyncClient(client.Client):
if self.read_loop_task:
await self.read_loop_task
- async def send(self, data, binary=None):
+ async def send(self, data):
"""Send a message to a client.
:param data: The data to send to the client. Data can be of type
``str``, ``bytes``, ``list`` or ``dict``. If a ``list``
or ``dict``, the data will be serialized as JSON.
- :param binary: ``True`` to send packet as binary, ``False`` to send
- as text. If not given, unicode (Python 2) and str
- (Python 3) are sent as text, and str (Python 2) and
- bytes (Python 3) are sent as binary.
Note: this method is a coroutine.
"""
- await self._send_packet(packet.Packet(packet.MESSAGE, data=data,
- binary=binary))
+ await self._send_packet(packet.Packet(packet.MESSAGE, data=data))
async def disconnect(self, abort=False):
"""Disconnect from the server.
@@ -182,14 +212,20 @@ class AsyncClient(client.Client):
raise exceptions.ConnectionError(
'Connection refused by the server')
if r.status < 200 or r.status >= 300:
+ self._reset()
+ try:
+ arg = await r.json()
+ except aiohttp.ClientError:
+ arg = None
raise exceptions.ConnectionError(
'Unexpected status code {} in server response'.format(
- r.status))
+ r.status), arg)
try:
- p = payload.Payload(encoded_payload=await r.read())
+ p = payload.Payload(encoded_payload=(await r.read()).decode(
+ 'utf-8'))
except ValueError:
- six.raise_from(exceptions.ConnectionError(
- 'Unexpected response from server'), None)
+ raise exceptions.ConnectionError(
+ 'Unexpected response from server') from None
open_packet = p.packets[0]
if open_packet.packet_type != packet.OPEN:
raise exceptions.ConnectionError(
@@ -198,8 +234,8 @@ class AsyncClient(client.Client):
'Polling connection accepted with ' + str(open_packet.data))
self.sid = open_packet.data['sid']
self.upgrades = open_packet.data['upgrades']
- self.ping_interval = open_packet.data['pingInterval'] / 1000.0
- self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0
+ self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0
+ self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0
self.current_transport = 'polling'
self.base_url += '&sid=' + self.sid
@@ -216,7 +252,6 @@ class AsyncClient(client.Client):
# upgrade to websocket succeeded, we're done here
return
- self.ping_loop_task = self.start_background_task(self._ping_loop)
self.write_loop_task = self.start_background_task(self._write_loop)
self.read_loop_task = self.start_background_task(
self._read_loop_polling)
@@ -239,6 +274,20 @@ class AsyncClient(client.Client):
self.logger.info(
'Attempting WebSocket connection to ' + websocket_url)
+ if self.http is None or self.http.closed: # pragma: no cover
+ self.http = aiohttp.ClientSession()
+
+ # extract any new cookies passed in a header so that they can also be
+ # sent the the WebSocket route
+ cookies = {}
+ for header, value in headers.items():
+ if header.lower() == 'cookie':
+ cookies = dict(
+ [cookie.split('=', 1) for cookie in value.split('; ')])
+ del headers[header]
+ break
+ self.http.cookie_jar.update_cookies(cookies)
+
try:
if not self.ssl_verify:
ssl_context = ssl.create_default_context()
@@ -252,7 +301,8 @@ class AsyncClient(client.Client):
websocket_url + self._get_url_timestamp(),
headers=headers)
except (aiohttp.client_exceptions.WSServerHandshakeError,
- aiohttp.client_exceptions.ServerConnectionError):
+ aiohttp.client_exceptions.ServerConnectionError,
+ aiohttp.client_exceptions.ClientConnectionError):
if upgrade:
self.logger.warning(
'WebSocket upgrade failed: connection error')
@@ -260,8 +310,7 @@ class AsyncClient(client.Client):
else:
raise exceptions.ConnectionError('Connection error')
if upgrade:
- p = packet.Packet(packet.PING, data='probe').encode(
- always_bytes=False)
+ p = packet.Packet(packet.PING, data='probe').encode()
try:
await ws.send_str(p)
except Exception as e: # pragma: no cover
@@ -281,7 +330,7 @@ class AsyncClient(client.Client):
self.logger.warning(
'WebSocket upgrade failed: no PONG packet')
return False
- p = packet.Packet(packet.UPGRADE).encode(always_bytes=False)
+ p = packet.Packet(packet.UPGRADE).encode()
try:
await ws.send_str(p)
except Exception as e: # pragma: no cover
@@ -304,8 +353,8 @@ class AsyncClient(client.Client):
'WebSocket connection accepted with ' + str(open_packet.data))
self.sid = open_packet.data['sid']
self.upgrades = open_packet.data['upgrades']
- self.ping_interval = open_packet.data['pingInterval'] / 1000.0
- self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0
+ self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0
+ self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0
self.current_transport = 'websocket'
self.state = 'connected'
@@ -313,7 +362,6 @@ class AsyncClient(client.Client):
await self._trigger_event('connect', run_async=False)
self.ws = ws
- self.ping_loop_task = self.start_background_task(self._ping_loop)
self.write_loop_task = self.start_background_task(self._write_loop)
self.read_loop_task = self.start_background_task(
self._read_loop_websocket)
@@ -328,8 +376,8 @@ class AsyncClient(client.Client):
pkt.data if not isinstance(pkt.data, bytes) else '<binary>')
if pkt.packet_type == packet.MESSAGE:
await self._trigger_event('message', pkt.data, run_async=True)
- elif pkt.packet_type == packet.PONG:
- self.pong_received = True
+ elif pkt.packet_type == packet.PING:
+ await self._send_packet(packet.Packet(packet.PONG, pkt.data))
elif pkt.packet_type == packet.CLOSE:
await self.disconnect(abort=True)
elif pkt.packet_type == packet.NOOP:
@@ -406,33 +454,6 @@ class AsyncClient(client.Client):
return False
return ret
- async def _ping_loop(self):
- """This background task sends a PING to the server at the requested
- interval.
- """
- self.pong_received = True
- if self.ping_loop_event is None:
- self.ping_loop_event = self.create_event()
- else:
- self.ping_loop_event.clear()
- while self.state == 'connected':
- if not self.pong_received:
- self.logger.info(
- 'PONG response has not been received, aborting')
- if self.ws:
- await self.ws.close()
- await self.queue.put(None)
- break
- self.pong_received = False
- await self._send_packet(packet.Packet(packet.PING))
- try:
- await asyncio.wait_for(self.ping_loop_event.wait(),
- self.ping_interval)
- except (asyncio.TimeoutError,
- asyncio.CancelledError): # pragma: no cover
- pass
- self.logger.info('Exiting ping task')
-
async def _read_loop_polling(self):
"""Read packets by polling the Engine.IO server."""
while self.state == 'connected':
@@ -452,7 +473,8 @@ class AsyncClient(client.Client):
await self.queue.put(None)
break
try:
- p = payload.Payload(encoded_payload=await r.read())
+ p = payload.Payload(encoded_payload=(await r.read()).decode(
+ 'utf-8'))
except ValueError:
self.logger.warning(
'Unexpected packet from server, aborting')
@@ -463,10 +485,6 @@ class AsyncClient(client.Client):
self.logger.info('Waiting for write loop task to end')
await self.write_loop_task
- self.logger.info('Waiting for ping loop task to end')
- if self.ping_loop_event: # pragma: no cover
- self.ping_loop_event.set()
- await self.ping_loop_task
if self.state == 'connected':
await self._trigger_event('disconnect', run_async=False)
try:
@@ -481,7 +499,17 @@ class AsyncClient(client.Client):
while self.state == 'connected':
p = None
try:
- p = (await self.ws.receive()).data
+ p = await asyncio.wait_for(
+ self.ws.receive(),
+ timeout=self.ping_interval + self.ping_timeout)
+ p = p.data
+ if p is None: # pragma: no cover
+ break # the connection is broken
+ except asyncio.TimeoutError:
+ self.logger.warning(
+ 'Server has stopped communicating, aborting')
+ await self.queue.put(None)
+ break
except aiohttp.client_exceptions.ServerDisconnectedError:
self.logger.info(
'Read loop: WebSocket connection was closed, aborting')
@@ -489,20 +517,21 @@ class AsyncClient(client.Client):
break
except Exception as e:
self.logger.info(
- 'Unexpected error "%s", aborting', str(e))
+ 'Unexpected error receiving packet: "%s", aborting',
+ str(e))
+ await self.queue.put(None)
+ break
+ try:
+ pkt = packet.Packet(encoded_packet=p)
+ except Exception as e: # pragma: no cover
+ self.logger.info(
+ 'Unexpected error decoding packet: "%s", aborting', str(e))
await self.queue.put(None)
break
- if isinstance(p, six.text_type): # pragma: no cover
- p = p.encode('utf-8')
- pkt = packet.Packet(encoded_packet=p)
await self._receive_packet(pkt)
self.logger.info('Waiting for write loop task to end')
await self.write_loop_task
- self.logger.info('Waiting for ping loop task to end')
- if self.ping_loop_event: # pragma: no cover
- self.ping_loop_event.set()
- await self.ping_loop_task
if self.state == 'connected':
await self._trigger_event('disconnect', run_async=False)
try:
@@ -566,13 +595,12 @@ class AsyncClient(client.Client):
try:
for pkt in packets:
if pkt.binary:
- await self.ws.send_bytes(pkt.encode(
- always_bytes=False))
+ await self.ws.send_bytes(pkt.encode())
else:
- await self.ws.send_str(pkt.encode(
- always_bytes=False))
+ await self.ws.send_str(pkt.encode())
self.queue.task_done()
- except aiohttp.client_exceptions.ServerDisconnectedError:
+ except (aiohttp.client_exceptions.ServerDisconnectedError,
+ BrokenPipeError, OSError):
self.logger.info(
'Write loop: WebSocket connection was closed, '
'aborting')
diff --git a/engineio/asyncio_server.py b/engineio/asyncio_server.py
index d52b556..6639f26 100644
--- a/engineio/asyncio_server.py
+++ b/engineio/asyncio_server.py
@@ -1,7 +1,5 @@
import asyncio
-
-import six
-from six.moves import urllib
+import urllib
from . import exceptions
from . import packet
@@ -24,23 +22,30 @@ class AsyncServer(server.Server):
"tornado", and finally "asgi". The first async mode that
has all its dependencies installed is the one that is
chosen.
- :param ping_timeout: The time in seconds that the client waits for the
- server to respond before disconnecting.
- :param ping_interval: The interval in seconds at which the client pings
- the server. The default is 25 seconds. For advanced
+ :param ping_interval: The interval in seconds at which the server pings
+ the client. The default is 25 seconds. For advanced
control, a two element tuple can be given, where
the first number is the ping interval and the second
- is a grace period added by the server. The default
- grace period is 5 seconds.
+ is a grace period added by the server.
+ :param ping_timeout: The time in seconds that the client waits for the
+ server to respond before disconnecting. The default
+ is 5 seconds.
:param max_http_buffer_size: The maximum size of a message when using the
- polling transport.
+ polling transport. The default is 1,000,000
+ bytes.
:param allow_upgrades: Whether to allow transport upgrades or not.
:param http_compression: Whether to compress packages when using the
polling transport.
:param compression_threshold: Only compress messages when their byte size
is greater than this value.
- :param cookie: Name of the HTTP cookie that contains the client session
- id. If set to ``None``, a cookie is not sent to the client.
+ :param cookie: If set to a string, it is the name of the HTTP cookie the
+ server sends back tot he client containing the client
+ session id. If set to a dictionary, the ``'name'`` key
+ contains the cookie name and other keys define cookie
+ attributes, where the value of each attribute can be a
+ string, a callable with no arguments, or a boolean. If set
+ to ``None`` (the default), a cookie is not sent to the
+ client.
:param cors_allowed_origins: Origin or list of origins that are allowed to
connect to this server. Only the same origin
is allowed by default. Set this argument to
@@ -49,7 +54,8 @@ class AsyncServer(server.Server):
:param cors_credentials: Whether credentials (cookies, authentication) are
allowed in requests to this server.
:param logger: To enable logging set to ``True`` or pass a logger object to
- use. To disable logging set to ``False``.
+ use. To disable logging set to ``False``. Note that fatal
+ errors are logged even when ``logger`` is ``False``.
:param json: An alternative json module to use for encoding and decoding
packets. Custom json modules must have ``dumps`` and ``loads``
functions that are compatible with the standard library
@@ -71,17 +77,13 @@ class AsyncServer(server.Server):
engineio_path = engineio_path.strip('/')
self._async['create_route'](app, self, '/{}/'.format(engineio_path))
- async def send(self, sid, data, binary=None):
+ async def send(self, sid, data):
"""Send a message to a client.
:param sid: The session id of the recipient client.
:param data: The data to send to the client. Data can be of type
``str``, ``bytes``, ``list`` or ``dict``. If a ``list``
or ``dict``, the data will be serialized as JSON.
- :param binary: ``True`` to send packet as binary, ``False`` to send
- as text. If not given, unicode (Python 2) and str
- (Python 3) are sent as text, and str (Python 2) and
- bytes (Python 3) are sent as binary.
Note: this method is a coroutine.
"""
@@ -91,8 +93,7 @@ class AsyncServer(server.Server):
# the socket is not available
self.logger.warning('Cannot send to sid %s', sid)
return
- await socket.send(packet.Packet(packet.MESSAGE, data=data,
- binary=binary))
+ await socket.send(packet.Packet(packet.MESSAGE, data=data))
async def get_session(self, sid):
"""Return the user session for a client.
@@ -172,7 +173,7 @@ class AsyncServer(server.Server):
del self.sockets[sid]
else:
await asyncio.wait([client.close()
- for client in six.itervalues(self.sockets)])
+ for client in self.sockets.values()])
self.sockets = {}
async def handle_request(self, *args, **kwargs):
@@ -198,28 +199,32 @@ class AsyncServer(server.Server):
allowed_origins = self._cors_allowed_origins(environ)
if allowed_origins is not None and origin not in \
allowed_origins:
- self.logger.info(origin + ' is not an accepted origin.')
- r = self._bad_request()
- make_response = self._async['make_response']
- if asyncio.iscoroutinefunction(make_response):
- response = await make_response(
- r['status'], r['headers'], r['response'], environ)
- else:
- response = make_response(r['status'], r['headers'],
- r['response'], environ)
- return response
+ self._log_error_once(
+ origin + ' is not an accepted origin.', 'bad-origin')
+ return await self._make_response(
+ self._bad_request(
+ origin + ' is not an accepted origin.'),
+ environ)
method = environ['REQUEST_METHOD']
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', ''))
sid = query['sid'][0] if 'sid' in query else None
- b64 = False
jsonp = False
jsonp_index = None
- if 'b64' in query:
- if query['b64'][0] == "1" or query['b64'][0].lower() == "true":
- b64 = True
+ # make sure the client speaks a compatible Engine.IO version
+ sid = query['sid'][0] if 'sid' in query else None
+ if sid is None and query.get('EIO') != ['4']:
+ self._log_error_once(
+ 'The client is using an unsupported version of the Socket.IO '
+ 'or Engine.IO protocols', 'bad-version'
+ )
+ return await self._make_response(self._bad_request(
+ 'The client is using an unsupported version of the Socket.IO '
+ 'or Engine.IO protocols'
+ ), environ)
+
if 'j' in query:
jsonp = True
try:
@@ -229,28 +234,34 @@ class AsyncServer(server.Server):
pass
if jsonp and jsonp_index is None:
- self.logger.warning('Invalid JSONP index number')
- r = self._bad_request()
+ self._log_error_once('Invalid JSONP index number',
+ 'bad-jsonp-index')
+ r = self._bad_request('Invalid JSONP index number')
elif method == 'GET':
if sid is None:
transport = query.get('transport', ['polling'])[0]
- if transport != 'polling' and transport != 'websocket':
- self.logger.warning('Invalid transport %s', transport)
- r = self._bad_request()
- else:
+ # transport must be one of 'polling' or 'websocket'.
+ # if 'websocket', the HTTP_UPGRADE header must match.
+ upgrade_header = environ.get('HTTP_UPGRADE').lower() \
+ if 'HTTP_UPGRADE' in environ else None
+ if transport == 'polling' \
+ or transport == upgrade_header == 'websocket':
r = await self._handle_connect(environ, transport,
- b64, jsonp_index)
+ jsonp_index)
+ else:
+ self._log_error_once('Invalid transport ' + transport,
+ 'bad-transport')
+ r = self._bad_request('Invalid transport ' + transport)
else:
if sid not in self.sockets:
- self.logger.warning('Invalid session %s', sid)
- r = self._bad_request()
+ self._log_error_once('Invalid session ' + sid, 'bad-sid')
+ r = self._bad_request('Invalid session ' + sid)
else:
socket = self._get_socket(sid)
try:
packets = await socket.handle_get_request(environ)
if isinstance(packets, list):
- r = self._ok(packets, b64=b64,
- jsonp_index=jsonp_index)
+ r = self._ok(packets, jsonp_index=jsonp_index)
else:
r = packets
except exceptions.EngineIOError:
@@ -261,8 +272,8 @@ class AsyncServer(server.Server):
del self.sockets[sid]
elif method == 'POST':
if sid is None or sid not in self.sockets:
- self.logger.warning('Invalid session %s', sid)
- r = self._bad_request()
+ self._log_error_once('Invalid session ' + sid, 'bad-sid')
+ r = self._bad_request('Invalid session ' + sid)
else:
socket = self._get_socket(sid)
try:
@@ -294,16 +305,7 @@ class AsyncServer(server.Server):
getattr(self, '_' + encoding)(r['response'])
r['headers'] += [('Content-Encoding', encoding)]
break
- cors_headers = self._cors_headers(environ)
- make_response = self._async['make_response']
- if asyncio.iscoroutinefunction(make_response):
- response = await make_response(r['status'],
- r['headers'] + cors_headers,
- r['response'], environ)
- else:
- response = make_response(r['status'], r['headers'] + cors_headers,
- r['response'], environ)
- return response
+ return await self._make_response(r, environ)
def start_background_task(self, target, *args, **kwargs):
"""Start a background task using the appropriate async model.
@@ -362,15 +364,29 @@ class AsyncServer(server.Server):
"""
return asyncio.Event(*args, **kwargs)
- async def _handle_connect(self, environ, transport, b64=False,
- jsonp_index=None):
+ async def _make_response(self, response_dict, environ):
+ cors_headers = self._cors_headers(environ)
+ make_response = self._async['make_response']
+ if asyncio.iscoroutinefunction(make_response):
+ response = await make_response(
+ response_dict['status'],
+ response_dict['headers'] + cors_headers,
+ response_dict['response'], environ)
+ else:
+ response = make_response(
+ response_dict['status'],
+ response_dict['headers'] + cors_headers,
+ response_dict['response'], environ)
+ return response
+
+ async def _handle_connect(self, environ, transport, jsonp_index=None):
"""Handle a client connection request."""
if self.start_service_task:
# start the service task to monitor connected clients
self.start_service_task = False
self.start_background_task(self._service_task)
- sid = self._generate_id()
+ sid = self.generate_id()
s = asyncio_socket.AsyncSocket(self, sid)
self.sockets[sid] = s
@@ -380,17 +396,18 @@ class AsyncServer(server.Server):
'pingTimeout': int(self.ping_timeout * 1000),
'pingInterval': int(self.ping_interval * 1000)})
await s.send(pkt)
+ s.schedule_ping()
ret = await self._trigger_event('connect', sid, environ,
run_async=False)
- if ret is False:
+ if ret is not None and ret is not True:
del self.sockets[sid]
self.logger.warning('Application rejected connection')
- return self._unauthorized()
+ return self._unauthorized(ret or None)
if transport == 'websocket':
ret = await s.handle_get_request(environ)
- if s.closed:
+ if s.closed and sid in self.sockets:
# websocket connection ended, so we are done
del self.sockets[sid]
return ret
@@ -398,9 +415,20 @@ class AsyncServer(server.Server):
s.connected = True
headers = None
if self.cookie:
- headers = [('Set-Cookie', self.cookie + '=' + sid)]
+ if isinstance(self.cookie, dict):
+ headers = [(
+ 'Set-Cookie',
+ self._generate_sid_cookie(sid, self.cookie)
+ )]
+ else:
+ headers = [(
+ 'Set-Cookie',
+ self._generate_sid_cookie(sid, {
+ 'name': self.cookie, 'path': '/', 'SameSite': 'Lax'
+ })
+ )]
try:
- return self._ok(await s.poll(), headers=headers, b64=b64,
+ return self._ok(await s.poll(), headers=headers,
jsonp_index=jsonp_index)
except exceptions.QueueEmpty:
return self._bad_request()
@@ -459,7 +487,12 @@ class AsyncServer(server.Server):
if not socket.closing and not socket.closed:
await socket.check_ping_timeout()
await self.sleep(sleep_interval)
- except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
+ except (
+ SystemExit,
+ KeyboardInterrupt,
+ asyncio.CancelledError,
+ GeneratorExit,
+ ):
self.logger.info('service task canceled')
break
except:
diff --git a/engineio/asyncio_socket.py b/engineio/asyncio_socket.py
index 7057a6c..508ee3c 100644
--- a/engineio/asyncio_socket.py
+++ b/engineio/asyncio_socket.py
@@ -1,5 +1,4 @@
import asyncio
-import six
import sys
import time
@@ -13,18 +12,24 @@ class AsyncSocket(socket.Socket):
async def poll(self):
"""Wait for packets to send to the client."""
try:
- packets = [await asyncio.wait_for(self.queue.get(),
- self.server.ping_timeout)]
+ packets = [await asyncio.wait_for(
+ self.queue.get(),
+ self.server.ping_interval + self.server.ping_timeout)]
self.queue.task_done()
except (asyncio.TimeoutError, asyncio.CancelledError):
raise exceptions.QueueEmpty()
if packets == [None]:
return []
- try:
- packets.append(self.queue.get_nowait())
- self.queue.task_done()
- except asyncio.QueueEmpty:
- pass
+ while True:
+ try:
+ pkt = self.queue.get_nowait()
+ self.queue.task_done()
+ if pkt is None:
+ self.queue.put_nowait(None)
+ break
+ packets.append(pkt)
+ except asyncio.QueueEmpty:
+ break
return packets
async def receive(self, pkt):
@@ -33,9 +38,8 @@ class AsyncSocket(socket.Socket):
self.sid, packet.packet_names[pkt.packet_type],
pkt.data if not isinstance(pkt.data, bytes)
else '<binary>')
- if pkt.packet_type == packet.PING:
- self.last_ping = time.time()
- await self.send(packet.Packet(packet.PONG, pkt.data))
+ if pkt.packet_type == packet.PONG:
+ self.schedule_ping()
elif pkt.packet_type == packet.MESSAGE:
await self.server._trigger_event(
'message', self.sid, pkt.data,
@@ -48,14 +52,11 @@ class AsyncSocket(socket.Socket):
raise exceptions.UnknownPacketError()
async def check_ping_timeout(self):
- """Make sure the client is still sending pings.
-
- This helps detect disconnections for long-polling clients.
- """
+ """Make sure the client is still sending pings."""
if self.closed:
raise exceptions.SocketIsClosedError()
- if time.time() - self.last_ping > self.server.ping_interval + \
- self.server.ping_interval_grace_period:
+ if self.last_ping and \
+ time.time() - self.last_ping > self.server.ping_timeout:
self.server.logger.info('%s: Client is gone, closing socket',
self.sid)
# Passing abort=False here will cause close() to write a
@@ -69,8 +70,6 @@ class AsyncSocket(socket.Socket):
"""Send a packet to the client."""
if not await self.check_ping_timeout():
return
- if self.upgrading:
- self.packet_backlog.append(pkt)
else:
await self.queue.put(pkt)
self.server.logger.info('%s: Sending packet %s data %s',
@@ -88,12 +87,16 @@ class AsyncSocket(socket.Socket):
self.server.logger.info('%s: Received request to upgrade to %s',
self.sid, transport)
return await getattr(self, '_upgrade_' + transport)(environ)
+ if self.upgrading or self.upgraded:
+ # we are upgrading to WebSocket, do not return any more packets
+ # through the polling endpoint
+ return [packet.Packet(packet.NOOP)]
try:
packets = await self.poll()
except exceptions.QueueEmpty:
exc = sys.exc_info()
await self.close(wait=False)
- six.reraise(*exc)
+ raise exc[1].with_traceback(exc[2])
return packets
async def handle_post_request(self, environ):
@@ -102,7 +105,7 @@ class AsyncSocket(socket.Socket):
if length > self.server.max_http_buffer_size:
raise exceptions.ContentTooLongError()
else:
- body = await environ['wsgi.input'].read(length)
+ body = (await environ['wsgi.input'].read(length)).decode('utf-8')
p = payload.Payload(encoded_payload=body)
for pkt in p.packets:
await self.receive(pkt)
@@ -118,6 +121,16 @@ class AsyncSocket(socket.Socket):
if wait:
await self.queue.join()
+ def schedule_ping(self):
+ async def send_ping():
+ self.last_ping = None
+ await asyncio.sleep(self.server.ping_interval)
+ if not self.closing and not self.closed:
+ self.last_ping = time.time()
+ await self.send(packet.Packet(packet.PING))
+
+ self.server.start_background_task(send_ping)
+
async def _upgrade_websocket(self, environ):
"""Upgrade the connection from polling to websocket."""
if self.upgraded:
@@ -143,15 +156,15 @@ class AsyncSocket(socket.Socket):
decoded_pkt.data != 'probe':
self.server.logger.info(
'%s: Failed websocket upgrade, no PING packet', self.sid)
+ self.upgrading = False
return
- await ws.send(packet.Packet(
- packet.PONG,
- data=six.text_type('probe')).encode(always_bytes=False))
+ await ws.send(packet.Packet(packet.PONG, data='probe').encode())
await self.queue.put(packet.Packet(packet.NOOP)) # end poll
try:
pkt = await ws.wait()
except IOError: # pragma: no cover
+ self.upgrading = False
return
decoded_pkt = packet.Packet(encoded_packet=pkt)
if decoded_pkt.packet_type != packet.UPGRADE:
@@ -160,13 +173,9 @@ class AsyncSocket(socket.Socket):
('%s: Failed websocket upgrade, expected UPGRADE packet, '
'received %s instead.'),
self.sid, pkt)
+ self.upgrading = False
return
self.upgraded = True
-
- # flush any packets that were sent during the upgrade
- for pkt in self.packet_backlog:
- await self.queue.put(pkt)
- self.packet_backlog = []
self.upgrading = False
else:
self.connected = True
@@ -185,7 +194,7 @@ class AsyncSocket(socket.Socket):
break
try:
for pkt in packets:
- await ws.send(pkt.encode(always_bytes=False))
+ await ws.send(pkt.encode())
except:
break
writer_task = asyncio.ensure_future(writer())
@@ -197,7 +206,9 @@ class AsyncSocket(socket.Socket):
p = None
wait_task = asyncio.ensure_future(ws.wait())
try:
- p = await asyncio.wait_for(wait_task, self.server.ping_timeout)
+ p = await asyncio.wait_for(
+ wait_task,
+ self.server.ping_interval + self.server.ping_timeout)
except asyncio.CancelledError: # pragma: no cover
# there is a bug (https://bugs.python.org/issue30508) in
# asyncio that causes a "Task exception never retrieved" error
@@ -216,8 +227,6 @@ class AsyncSocket(socket.Socket):
if p is None:
# connection closed by client
break
- if isinstance(p, six.text_type): # pragma: no cover
- p = p.encode('utf-8')
pkt = packet.Packet(encoded_packet=p)
try:
await self.receive(pkt)
diff --git a/engineio/client.py b/engineio/client.py
index 0071eeb..524c1a6 100644
--- a/engineio/client.py
+++ b/engineio/client.py
@@ -1,3 +1,5 @@
+from base64 import b64encode
+from json import JSONDecodeError
import logging
try:
import queue
@@ -7,9 +9,8 @@ import signal
import ssl
import threading
import time
+import urllib
-import six
-from six.moves import urllib
try:
import requests
except ImportError: # pragma: no cover
@@ -25,9 +26,6 @@ from . import payload
default_logger = logging.getLogger('engineio.client')
connected_clients = []
-if six.PY2: # pragma: no cover
- ConnectionError = OSError
-
def signal_handler(sig, frame):
"""SIGINT handler.
@@ -35,10 +33,8 @@ def signal_handler(sig, frame):
Disconnect all active clients and then invoke the original signal handler.
"""
for client in connected_clients[:]:
- if client.is_asyncio_based():
- client.start_background_task(client.disconnect, abort=True)
- else:
- client.disconnect(abort=True)
+ if not client.is_asyncio_based():
+ client.disconnect()
if callable(original_signal_handler):
return original_signal_handler(sig, frame)
else: # pragma: no cover
@@ -46,7 +42,7 @@ def signal_handler(sig, frame):
return signal.default_int_handler(sig, frame)
-original_signal_handler = signal.signal(signal.SIGINT, signal_handler)
+original_signal_handler = None
class Client(object):
@@ -57,13 +53,18 @@ class Client(object):
:param logger: To enable logging set to ``True`` or pass a logger object to
use. To disable logging set to ``False``. The default is
- ``False``.
+ ``False``. Note that fatal errors are logged even when
+ ``logger`` is ``False``.
:param json: An alternative json module to use for encoding and decoding
packets. Custom json modules must have ``dumps`` and ``loads``
functions that are compatible with the standard library
versions.
:param request_timeout: A timeout in seconds for requests. The default is
5 seconds.
+ :param http_session: an initialized ``requests.Session`` object to be used
+ when sending requests to the server. Use it if you
+ need to add special client options such as proxy
+ servers, SSL certificates, etc.
:param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to
skip SSL certificate verification, allowing
connections to servers with self signed certificates.
@@ -75,7 +76,13 @@ class Client(object):
logger=False,
json=None,
request_timeout=5,
+ http_session=None,
ssl_verify=True):
+ global original_signal_handler
+ if original_signal_handler is None and \
+ threading.current_thread() == threading.main_thread():
+ original_signal_handler = signal.signal(signal.SIGINT,
+ signal_handler)
self.handlers = {}
self.base_url = None
self.transports = None
@@ -84,13 +91,10 @@ class Client(object):
self.upgrades = None
self.ping_interval = None
self.ping_timeout = None
- self.pong_received = True
- self.http = None
+ self.http = http_session
self.ws = None
self.read_loop_task = None
self.write_loop_task = None
- self.ping_loop_task = None
- self.ping_loop_event = None
self.queue = None
self.state = 'disconnected'
self.ssl_verify = ssl_verify
@@ -101,8 +105,7 @@ class Client(object):
self.logger = logger
else:
self.logger = default_logger
- if not logging.root.handlers and \
- self.logger.level == logging.NOTSET:
+ if self.logger.level == logging.NOTSET:
if logger:
self.logger.setLevel(logging.INFO)
else:
@@ -147,7 +150,7 @@ class Client(object):
return set_handler
set_handler(handler)
- def connect(self, url, headers={}, transports=None,
+ def connect(self, url, headers=None, transports=None,
engineio_path='engine.io'):
"""Connect to an Engine.IO server.
@@ -172,7 +175,7 @@ class Client(object):
raise ValueError('Client is not in a disconnected state')
valid_transports = ['polling', 'websocket']
if transports is not None:
- if isinstance(transports, six.string_types):
+ if isinstance(transports, str):
transports = [transports]
transports = [transport for transport in transports
if transport in valid_transports]
@@ -181,7 +184,7 @@ class Client(object):
self.transports = transports or valid_transports
self.queue = self.create_queue()
return getattr(self, '_connect_' + self.transports[0])(
- url, headers, engineio_path)
+ url, headers or {}, engineio_path)
def wait(self):
"""Wait until the connection with the server ends.
@@ -192,19 +195,14 @@ class Client(object):
if self.read_loop_task:
self.read_loop_task.join()
- def send(self, data, binary=None):
+ def send(self, data):
"""Send a message to a client.
:param data: The data to send to the client. Data can be of type
``str``, ``bytes``, ``list`` or ``dict``. If a ``list``
or ``dict``, the data will be serialized as JSON.
- :param binary: ``True`` to send packet as binary, ``False`` to send
- as text. If not given, unicode (Python 2) and str
- (Python 3) are sent as text, and str (Python 2) and
- bytes (Python 3) are sent as binary.
"""
- self._send_packet(packet.Packet(packet.MESSAGE, data=data,
- binary=binary))
+ self._send_packet(packet.Packet(packet.MESSAGE, data=data))
def disconnect(self, abort=False):
"""Disconnect from the server.
@@ -289,14 +287,19 @@ class Client(object):
raise exceptions.ConnectionError(
'Connection refused by the server')
if r.status_code < 200 or r.status_code >= 300:
+ self._reset()
+ try:
+ arg = r.json()
+ except JSONDecodeError:
+ arg = None
raise exceptions.ConnectionError(
'Unexpected status code {} in server response'.format(
- r.status_code))
+ r.status_code), arg)
try:
- p = payload.Payload(encoded_payload=r.content)
+ p = payload.Payload(encoded_payload=r.content.decode('utf-8'))
except ValueError:
- six.raise_from(exceptions.ConnectionError(
- 'Unexpected response from server'), None)
+ raise exceptions.ConnectionError(
+ 'Unexpected response from server') from None
open_packet = p.packets[0]
if open_packet.packet_type != packet.OPEN:
raise exceptions.ConnectionError(
@@ -305,8 +308,8 @@ class Client(object):
'Polling connection accepted with ' + str(open_packet.data))
self.sid = open_packet.data['sid']
self.upgrades = open_packet.data['upgrades']
- self.ping_interval = open_packet.data['pingInterval'] / 1000.0
- self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0
+ self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0
+ self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0
self.current_transport = 'polling'
self.base_url += '&sid=' + self.sid
@@ -324,7 +327,6 @@ class Client(object):
return
# start background tasks associated with this client
- self.ping_loop_task = self.start_background_task(self._ping_loop)
self.write_loop_task = self.start_background_task(self._write_loop)
self.read_loop_task = self.start_background_task(
self._read_loop_polling)
@@ -348,23 +350,71 @@ class Client(object):
self.logger.info(
'Attempting WebSocket connection to ' + websocket_url)
- # get the cookies from the long-polling connection so that they can
- # also be sent the the WebSocket route
+ # get cookies and other settings from the long-polling connection
+ # so that they are preserved when connecting to the WebSocket route
cookies = None
+ extra_options = {}
if self.http:
+ # cookies
cookies = '; '.join(["{}={}".format(cookie.name, cookie.value)
for cookie in self.http.cookies])
+ for header, value in headers.items():
+ if header.lower() == 'cookie':
+ if cookies:
+ cookies += '; '
+ cookies += value
+ del headers[header]
+ break
+ # auth
+ if 'Authorization' not in headers and self.http.auth is not None:
+ if not isinstance(self.http.auth, tuple): # pragma: no cover
+ raise ValueError('Only basic authentication is supported')
+ basic_auth = '{}:{}'.format(
+ self.http.auth[0], self.http.auth[1]).encode('utf-8')
+ basic_auth = b64encode(basic_auth).decode('utf-8')
+ headers['Authorization'] = 'Basic ' + basic_auth
+
+ # cert
+ # this can be given as ('certfile', 'keyfile') or just 'certfile'
+ if isinstance(self.http.cert, tuple):
+ extra_options['sslopt'] = {
+ 'certfile': self.http.cert[0],
+ 'keyfile': self.http.cert[1]}
+ elif self.http.cert:
+ extra_options['sslopt'] = {'certfile': self.http.cert}
+
+ # proxies
+ if self.http.proxies:
+ proxy_url = None
+ if websocket_url.startswith('ws://'):
+ proxy_url = self.http.proxies.get(
+ 'ws', self.http.proxies.get('http'))
+ else: # wss://
+ proxy_url = self.http.proxies.get(
+ 'wss', self.http.proxies.get('https'))
+ if proxy_url:
+ parsed_url = urllib.parse.urlparse(
+ proxy_url if '://' in proxy_url
+ else 'scheme://' + proxy_url)
+ extra_options['http_proxy_host'] = parsed_url.hostname
+ extra_options['http_proxy_port'] = parsed_url.port
+ extra_options['http_proxy_auth'] = (
+ (parsed_url.username, parsed_url.password)
+ if parsed_url.username or parsed_url.password
+ else None)
+
+ # verify
+ if not self.http.verify:
+ self.ssl_verify = False
+
+ if not self.ssl_verify:
+ extra_options['sslopt'] = {"cert_reqs": ssl.CERT_NONE}
try:
- if not self.ssl_verify:
- ws = websocket.create_connection(
- websocket_url + self._get_url_timestamp(), header=headers,
- cookie=cookies, sslopt={"cert_reqs": ssl.CERT_NONE})
- else:
- ws = websocket.create_connection(
- websocket_url + self._get_url_timestamp(), header=headers,
- cookie=cookies)
- except (ConnectionError, IOError):
+ ws = websocket.create_connection(
+ websocket_url + self._get_url_timestamp(), header=headers,
+ cookie=cookies, enable_multithread=True, **extra_options)
+ except (ConnectionError, IOError, websocket.WebSocketException):
if upgrade:
self.logger.warning(
'WebSocket upgrade failed: connection error')
@@ -372,8 +422,7 @@ class Client(object):
else:
raise exceptions.ConnectionError('Connection error')
if upgrade:
- p = packet.Packet(packet.PING,
- data=six.text_type('probe')).encode()
+ p = packet.Packet(packet.PING, data='probe').encode()
try:
ws.send(p)
except Exception as e: # pragma: no cover
@@ -416,17 +465,17 @@ class Client(object):
'WebSocket connection accepted with ' + str(open_packet.data))
self.sid = open_packet.data['sid']
self.upgrades = open_packet.data['upgrades']
- self.ping_interval = open_packet.data['pingInterval'] / 1000.0
- self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0
+ self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0
+ self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0
self.current_transport = 'websocket'
self.state = 'connected'
connected_clients.append(self)
self._trigger_event('connect', run_async=False)
self.ws = ws
+ self.ws.settimeout(self.ping_interval + self.ping_timeout)
# start background tasks associated with this client
- self.ping_loop_task = self.start_background_task(self._ping_loop)
self.write_loop_task = self.start_background_task(self._write_loop)
self.read_loop_task = self.start_background_task(
self._read_loop_websocket)
@@ -441,8 +490,8 @@ class Client(object):
pkt.data if not isinstance(pkt.data, bytes) else '<binary>')
if pkt.packet_type == packet.MESSAGE:
self._trigger_event('message', pkt.data, run_async=True)
- elif pkt.packet_type == packet.PONG:
- self.pong_received = True
+ elif pkt.packet_type == packet.PING:
+ self._send_packet(packet.Packet(packet.PONG, pkt.data))
elif pkt.packet_type == packet.CLOSE:
self.disconnect(abort=True)
elif pkt.packet_type == packet.NOOP:
@@ -500,7 +549,7 @@ class Client(object):
scheme += 's'
return ('{scheme}://{netloc}/{path}/?{query}'
- '{sep}transport={transport}&EIO=3').format(
+ '{sep}transport={transport}&EIO=4').format(
scheme=scheme, netloc=parsed_url.netloc,
path=engineio_path, query=parsed_url.query,
sep='&' if parsed_url.query else '',
@@ -510,28 +559,6 @@ class Client(object):
"""Generate the Engine.IO query string timestamp."""
return '&t=' + str(time.time())
- def _ping_loop(self):
- """This background task sends a PING to the server at the requested
- interval.
- """
- self.pong_received = True
- if self.ping_loop_event is None:
- self.ping_loop_event = self.create_event()
- else:
- self.ping_loop_event.clear()
- while self.state == 'connected':
- if not self.pong_received:
- self.logger.info(
- 'PONG response has not been received, aborting')
- if self.ws:
- self.ws.shutdown()
- self.queue.put(None)
- break
- self.pong_received = False
- self._send_packet(packet.Packet(packet.PING))
- self.ping_loop_event.wait(timeout=self.ping_interval)
- self.logger.info('Exiting ping task')
-
def _read_loop_polling(self):
"""Read packets by polling the Engine.IO server."""
while self.state == 'connected':
@@ -551,7 +578,7 @@ class Client(object):
self.queue.put(None)
break
try:
- p = payload.Payload(encoded_payload=r.content)
+ p = payload.Payload(encoded_payload=r.content.decode('utf-8'))
except ValueError:
self.logger.warning(
'Unexpected packet from server, aborting')
@@ -562,10 +589,6 @@ class Client(object):
self.logger.info('Waiting for write loop task to end')
self.write_loop_task.join()
- self.logger.info('Waiting for ping loop task to end')
- if self.ping_loop_event: # pragma: no cover
- self.ping_loop_event.set()
- self.ping_loop_task.join()
if self.state == 'connected':
self._trigger_event('disconnect', run_async=False)
try:
@@ -581,6 +604,11 @@ class Client(object):
p = None
try:
p = self.ws.recv()
+ except websocket.WebSocketTimeoutException:
+ self.logger.warning(
+ 'Server has stopped communicating, aborting')
+ self.queue.put(None)
+ break
except websocket.WebSocketConnectionClosedException:
self.logger.warning(
'WebSocket connection was closed, aborting')
@@ -588,20 +616,21 @@ class Client(object):
break
except Exception as e:
self.logger.info(
- 'Unexpected error "%s", aborting', str(e))
+ 'Unexpected error receiving packet: "%s", aborting',
+ str(e))
+ self.queue.put(None)
+ break
+ try:
+ pkt = packet.Packet(encoded_packet=p)
+ except Exception as e: # pragma: no cover
+ self.logger.info(
+ 'Unexpected error decoding packet: "%s", aborting', str(e))
self.queue.put(None)
break
- if isinstance(p, six.text_type): # pragma: no cover
- p = p.encode('utf-8')
- pkt = packet.Packet(encoded_packet=p)
self._receive_packet(pkt)
self.logger.info('Waiting for write loop task to end')
self.write_loop_task.join()
- self.logger.info('Waiting for ping loop task to end')
- if self.ping_loop_event: # pragma: no cover
- self.ping_loop_event.set()
- self.ping_loop_task.join()
if self.state == 'connected':
self._trigger_event('disconnect', run_async=False)
try:
@@ -663,13 +692,14 @@ class Client(object):
# websocket
try:
for pkt in packets:
- encoded_packet = pkt.encode(always_bytes=False)
+ encoded_packet = pkt.encode()
if pkt.binary:
self.ws.send_binary(encoded_packet)
else:
self.ws.send(encoded_packet)
self.queue.task_done()
- except websocket.WebSocketConnectionClosedException:
+ except (websocket.WebSocketConnectionClosedException,
+ BrokenPipeError, OSError):
self.logger.warning(
'WebSocket connection was closed, aborting')
break
diff --git a/engineio/packet.py b/engineio/packet.py
index a3aa6d4..9dbd6c6 100644
--- a/engineio/packet.py
+++ b/engineio/packet.py
@@ -1,12 +1,10 @@
import base64
import json as _json
-import six
-
(OPEN, CLOSE, PING, PONG, MESSAGE, UPGRADE, NOOP) = (0, 1, 2, 3, 4, 5, 6)
packet_names = ['OPEN', 'CLOSE', 'PING', 'PONG', 'MESSAGE', 'UPGRADE', 'NOOP']
-binary_types = (six.binary_type, bytearray)
+binary_types = (bytes, bytearray)
class Packet(object):
@@ -14,79 +12,61 @@ class Packet(object):
json = _json
- def __init__(self, packet_type=NOOP, data=None, binary=None,
- encoded_packet=None):
+ def __init__(self, packet_type=NOOP, data=None, encoded_packet=None):
self.packet_type = packet_type
self.data = data
- if binary is not None:
- self.binary = binary
- elif isinstance(data, six.text_type):
+ if isinstance(data, str):
self.binary = False
elif isinstance(data, binary_types):
self.binary = True
else:
self.binary = False
+ if self.binary and self.packet_type != MESSAGE:
+ raise ValueError('Binary packets can only be of type MESSAGE')
if encoded_packet:
self.decode(encoded_packet)
- def encode(self, b64=False, always_bytes=True):
+ def encode(self, b64=False):
"""Encode the packet for transmission."""
- if self.binary and not b64:
- encoded_packet = six.int2byte(self.packet_type)
- else:
- encoded_packet = six.text_type(self.packet_type)
- if self.binary and b64:
- encoded_packet = 'b' + encoded_packet
if self.binary:
if b64:
- encoded_packet += base64.b64encode(self.data).decode('utf-8')
+ encoded_packet = 'b' + base64.b64encode(self.data).decode(
+ 'utf-8')
else:
+ encoded_packet = self.data
+ else:
+ encoded_packet = str(self.packet_type)
+ if isinstance(self.data, str):
encoded_packet += self.data
- elif isinstance(self.data, six.string_types):
- encoded_packet += self.data
- elif isinstance(self.data, dict) or isinstance(self.data, list):
- encoded_packet += self.json.dumps(self.data,
- separators=(',', ':'))
- elif self.data is not None:
- encoded_packet += str(self.data)
- if always_bytes and not isinstance(encoded_packet, binary_types):
- encoded_packet = encoded_packet.encode('utf-8')
+ elif isinstance(self.data, dict) or isinstance(self.data, list):
+ encoded_packet += self.json.dumps(self.data,
+ separators=(',', ':'))
+ elif self.data is not None:
+ encoded_packet += str(self.data)
return encoded_packet
def decode(self, encoded_packet):
"""Decode a transmitted package."""
- b64 = False
- if not isinstance(encoded_packet, binary_types):
- encoded_packet = encoded_packet.encode('utf-8')
- elif not isinstance(encoded_packet, bytes):
- encoded_packet = bytes(encoded_packet)
- self.packet_type = six.byte2int(encoded_packet[0:1])
- if self.packet_type == 98: # 'b' --> binary base64 encoded packet
+ self.binary = isinstance(encoded_packet, binary_types)
+ b64 = not self.binary and encoded_packet[0] == 'b'
+ if b64:
self.binary = True
- encoded_packet = encoded_packet[1:]
- self.packet_type = six.byte2int(encoded_packet[0:1])
- self.packet_type -= 48
- b64 = True
- elif self.packet_type >= 48:
- self.packet_type -= 48
- self.binary = False
+ self.packet_type = MESSAGE
+ self.data = base64.b64decode(encoded_packet[1:])
else:
- self.binary = True
- self.data = None
- if len(encoded_packet) > 1:
+ if self.binary and not isinstance(encoded_packet, bytes):
+ encoded_packet = bytes(encoded_packet)
if self.binary:
- if b64:
- self.data = base64.b64decode(encoded_packet[1:])
- else:
- self.data = encoded_packet[1:]
+ self.packet_type = MESSAGE
+ self.data = encoded_packet
else:
+ self.packet_type = int(encoded_packet[0])
try:
- self.data = self.json.loads(
- encoded_packet[1:].decode('utf-8'))
+ self.data = self.json.loads(encoded_packet[1:])
if isinstance(self.data, int):
# do not allow integer payloads, see
# github.com/miguelgrinberg/python-engineio/issues/75
# for background on this decision
raise ValueError
except ValueError:
- self.data = encoded_packet[1:].decode('utf-8')
+ self.data = encoded_packet[1:]
diff --git a/engineio/payload.py b/engineio/payload.py
index fbf9cbd..f0e9e34 100644
--- a/engineio/payload.py
+++ b/engineio/payload.py
@@ -1,9 +1,7 @@
-import six
+import urllib
from . import packet
-from six.moves import urllib
-
class Payload(object):
"""Engine.IO payload."""
@@ -14,31 +12,19 @@ class Payload(object):
if encoded_payload is not None:
self.decode(encoded_payload)
- def encode(self, b64=False, jsonp_index=None):
+ def encode(self, jsonp_index=None):
"""Encode the payload for transmission."""
- encoded_payload = b''
+ encoded_payload = ''
for pkt in self.packets:
- encoded_packet = pkt.encode(b64=b64)
- packet_len = len(encoded_packet)
- if b64:
- encoded_payload += str(packet_len).encode('utf-8') + b':' + \
- encoded_packet
- else:
- binary_len = b''
- while packet_len != 0:
- binary_len = six.int2byte(packet_len % 10) + binary_len
- packet_len = int(packet_len / 10)
- if not pkt.binary:
- encoded_payload += b'\0'
- else:
- encoded_payload += b'\1'
- encoded_payload += binary_len + b'\xff' + encoded_packet
+ if encoded_payload:
+ encoded_payload += '\x1e'
+ encoded_payload += pkt.encode(b64=True)
if jsonp_index is not None:
- encoded_payload = b'___eio[' + \
- str(jsonp_index).encode() + \
- b']("' + \
- encoded_payload.replace(b'"', b'\\"') + \
- b'");'
+ encoded_payload = '___eio[' + \
+ str(jsonp_index) + \
+ ']("' + \
+ encoded_payload.replace('"', '\\"') + \
+ '");'
return encoded_payload
def decode(self, encoded_payload):
@@ -49,33 +35,12 @@ class Payload(object):
return
# JSONP POST payload starts with 'd='
- if encoded_payload.startswith(b'd='):
+ if encoded_payload.startswith('d='):
encoded_payload = urllib.parse.parse_qs(
- encoded_payload)[b'd'][0]
+ encoded_payload)['d'][0]
- i = 0
- if six.byte2int(encoded_payload[0:1]) <= 1:
- # binary encoding
- while i < len(encoded_payload):
- if len(self.packets) >= self.max_decode_packets:
- raise ValueError('Too many packets in payload')
- packet_len = 0
- i += 1
- while six.byte2int(encoded_payload[i:i + 1]) != 255:
- packet_len = packet_len * 10 + six.byte2int(
- encoded_payload[i:i + 1])
- i += 1
- self.packets.append(packet.Packet(
- encoded_packet=encoded_payload[i + 1:i + 1 + packet_len]))
- i += packet_len + 1
- else:
- # assume text encoding
- encoded_payload = encoded_payload.decode('utf-8')
- while i < len(encoded_payload):
- if len(self.packets) >= self.max_decode_packets:
- raise ValueError('Too many packets in payload')
- j = encoded_payload.find(':', i)
- packet_len = int(encoded_payload[i:j])
- pkt = encoded_payload[j + 1:j + 1 + packet_len]
- self.packets.append(packet.Packet(encoded_packet=pkt))
- i = j + 1 + packet_len
+ encoded_packets = encoded_payload.split('\x1e')
+ if len(encoded_packets) > self.max_decode_packets:
+ raise ValueError('Too many packets in payload')
+ self.packets = [packet.Packet(encoded_packet=encoded_packet)
+ for encoded_packet in encoded_packets]
diff --git a/engineio/server.py b/engineio/server.py
index e1543c2..703e7e9 100644
--- a/engineio/server.py
+++ b/engineio/server.py
@@ -1,12 +1,12 @@
+import base64
import gzip
import importlib
+import io
import logging
-import uuid
+import secrets
+import urllib
import zlib
-import six
-from six.moves import urllib
-
from . import exceptions
from . import packet
from . import payload
@@ -29,17 +29,16 @@ class Server(object):
"gevent_uwsgi", then "gevent", and finally "threading".
The first async mode that has all its dependencies
installed is the one that is chosen.
- :param ping_timeout: The time in seconds that the client waits for the
- server to respond before disconnecting. The default
- is 60 seconds.
- :param ping_interval: The interval in seconds at which the client pings
- the server. The default is 25 seconds. For advanced
+ :param ping_interval: The interval in seconds at which the server pings
+ the client. The default is 25 seconds. For advanced
control, a two element tuple can be given, where
the first number is the ping interval and the second
- is a grace period added by the server. The default
- grace period is 5 seconds.
+ is a grace period added by the server.
+ :param ping_timeout: The time in seconds that the client waits for the
+ server to respond before disconnecting. The default
+ is 5 seconds.
:param max_http_buffer_size: The maximum size of a message when using the
- polling transport. The default is 100,000,000
+ polling transport. The default is 1,000,000
bytes.
:param allow_upgrades: Whether to allow transport upgrades or not. The
default is ``True``.
@@ -48,9 +47,14 @@ class Server(object):
:param compression_threshold: Only compress messages when their byte size
is greater than this value. The default is
1024 bytes.
- :param cookie: Name of the HTTP cookie that contains the client session
- id. If set to ``None``, a cookie is not sent to the client.
- The default is ``'io'``.
+ :param cookie: If set to a string, it is the name of the HTTP cookie the
+ server sends back tot he client containing the client
+ session id. If set to a dictionary, the ``'name'`` key
+ contains the cookie name and other keys define cookie
+ attributes, where the value of each attribute can be a
+ string, a callable with no arguments, or a boolean. If set
+ to ``None`` (the default), a cookie is not sent to the
+ client.
:param cors_allowed_origins: Origin or list of origins that are allowed to
connect to this server. Only the same origin
is allowed by default. Set this argument to
@@ -61,7 +65,8 @@ class Server(object):
is ``True``.
:param logger: To enable logging set to ``True`` or pass a logger object to
use. To disable logging set to ``False``. The default is
- ``False``.
+ ``False``. Note that fatal errors are logged even when
+ ``logger`` is ``False``.
:param json: An alternative json module to use for encoding and decoding
packets. Custom json modules must have ``dumps`` and ``loads``
functions that are compatible with the standard library
@@ -79,11 +84,12 @@ class Server(object):
compression_methods = ['gzip', 'deflate']
event_names = ['connect', 'disconnect', 'message']
_default_monitor_clients = True
+ sequence_number = 0
- def __init__(self, async_mode=None, ping_timeout=60, ping_interval=25,
- max_http_buffer_size=100000000, allow_upgrades=True,
+ def __init__(self, async_mode=None, ping_interval=25, ping_timeout=5,
+ max_http_buffer_size=1000000, allow_upgrades=True,
http_compression=True, compression_threshold=1024,
- cookie='io', cors_allowed_origins=None,
+ cookie=None, cors_allowed_origins=None,
cors_credentials=True, logger=False, json=None,
async_handlers=True, monitor_clients=None, **kwargs):
self.ping_timeout = ping_timeout
@@ -92,7 +98,7 @@ class Server(object):
self.ping_interval_grace_period = ping_interval[1]
else:
self.ping_interval = ping_interval
- self.ping_interval_grace_period = 5
+ self.ping_interval_grace_period = 0
self.max_http_buffer_size = max_http_buffer_size
self.allow_upgrades = allow_upgrades
self.http_compression = http_compression
@@ -103,6 +109,7 @@ class Server(object):
self.async_handlers = async_handlers
self.sockets = {}
self.handlers = {}
+ self.log_message_keys = set()
self.start_service_task = monitor_clients \
if monitor_clients is not None else self._default_monitor_clients
if json is not None:
@@ -111,8 +118,7 @@ class Server(object):
self.logger = logger
else:
self.logger = default_logger
- if not logging.root.handlers and \
- self.logger.level == logging.NOTSET:
+ if self.logger.level == logging.NOTSET:
if logger:
self.logger.setLevel(logging.INFO)
else:
@@ -196,17 +202,13 @@ class Server(object):
return set_handler
set_handler(handler)
- def send(self, sid, data, binary=None):
+ def send(self, sid, data):
"""Send a message to a client.
:param sid: The session id of the recipient client.
:param data: The data to send to the client. Data can be of type
``str``, ``bytes``, ``list`` or ``dict``. If a ``list``
or ``dict``, the data will be serialized as JSON.
- :param binary: ``True`` to send packet as binary, ``False`` to send
- as text. If not given, unicode (Python 2) and str
- (Python 3) are sent as text, and str (Python 2) and
- bytes (Python 3) are sent as binary.
"""
try:
socket = self._get_socket(sid)
@@ -214,7 +216,7 @@ class Server(object):
# the socket is not available
self.logger.warning('Cannot send to sid %s', sid)
return
- socket.send(packet.Packet(packet.MESSAGE, data=data, binary=binary))
+ socket.send(packet.Packet(packet.MESSAGE, data=data))
def get_session(self, sid):
"""Return the user session for a client.
@@ -292,7 +294,7 @@ class Server(object):
if sid in self.sockets: # pragma: no cover
del self.sockets[sid]
else:
- for client in six.itervalues(self.sockets):
+ for client in self.sockets.values():
client.close()
self.sockets = {}
@@ -329,22 +331,30 @@ class Server(object):
allowed_origins = self._cors_allowed_origins(environ)
if allowed_origins is not None and origin not in \
allowed_origins:
- self.logger.info(origin + ' is not an accepted origin.')
- r = self._bad_request()
+ self._log_error_once(
+ origin + ' is not an accepted origin.', 'bad-origin')
+ r = self._bad_request(
+ origin + ' is not an accepted origin.')
start_response(r['status'], r['headers'])
return [r['response']]
method = environ['REQUEST_METHOD']
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', ''))
-
- sid = query['sid'][0] if 'sid' in query else None
- b64 = False
jsonp = False
jsonp_index = None
- if 'b64' in query:
- if query['b64'][0] == "1" or query['b64'][0].lower() == "true":
- b64 = True
+ # make sure the client speaks a compatible Engine.IO version
+ sid = query['sid'][0] if 'sid' in query else None
+ if sid is None and query.get('EIO') != ['4']:
+ self._log_error_once(
+ 'The client is using an unsupported version of the Socket.IO '
+ 'or Engine.IO protocols', 'bad-version')
+ r = self._bad_request(
+ 'The client is using an unsupported version of the Socket.IO '
+ 'or Engine.IO protocols')
+ start_response(r['status'], r['headers'])
+ return [r['response']]
+
if 'j' in query:
jsonp = True
try:
@@ -354,29 +364,35 @@ class Server(object):
pass
if jsonp and jsonp_index is None:
- self.logger.warning('Invalid JSONP index number')
- r = self._bad_request()
+ self._log_error_once('Invalid JSONP index number',
+ 'bad-jsonp-index')
+ r = self._bad_request('Invalid JSONP index number')
elif method == 'GET':
if sid is None:
transport = query.get('transport', ['polling'])[0]
- if transport != 'polling' and transport != 'websocket':
- self.logger.warning('Invalid transport %s', transport)
- r = self._bad_request()
- else:
+ # transport must be one of 'polling' or 'websocket'.
+ # if 'websocket', the HTTP_UPGRADE header must match.
+ upgrade_header = environ.get('HTTP_UPGRADE').lower() \
+ if 'HTTP_UPGRADE' in environ else None
+ if transport == 'polling' \
+ or transport == upgrade_header == 'websocket':
r = self._handle_connect(environ, start_response,
- transport, b64, jsonp_index)
+ transport, jsonp_index)
+ else:
+ self._log_error_once('Invalid transport ' + transport,
+ 'bad-transport')
+ r = self._bad_request('Invalid transport ' + transport)
else:
if sid not in self.sockets:
- self.logger.warning('Invalid session %s', sid)
- r = self._bad_request()
+ self._log_error_once('Invalid session ' + sid, 'bad-sid')
+ r = self._bad_request('Invalid session ' + sid)
else:
socket = self._get_socket(sid)
try:
packets = socket.handle_get_request(
environ, start_response)
if isinstance(packets, list):
- r = self._ok(packets, b64=b64,
- jsonp_index=jsonp_index)
+ r = self._ok(packets, jsonp_index=jsonp_index)
else:
r = packets
except exceptions.EngineIOError:
@@ -387,8 +403,8 @@ class Server(object):
del self.sockets[sid]
elif method == 'POST':
if sid is None or sid not in self.sockets:
- self.logger.warning('Invalid session %s', sid)
- r = self._bad_request()
+ self._log_error_once('Invalid session ' + sid, 'bad-sid')
+ r = self._bad_request('Invalid session ' + sid)
else:
socket = self._get_socket(sid)
try:
@@ -481,11 +497,28 @@ class Server(object):
"""
return self._async['event'](*args, **kwargs)
- def _generate_id(self):
+ def generate_id(self):
"""Generate a unique session id."""
- return uuid.uuid4().hex
+ id = base64.b64encode(
+ secrets.token_bytes(12) + self.sequence_number.to_bytes(3, 'big'))
+ self.sequence_number = (self.sequence_number + 1) & 0xffffff
+ return id.decode('utf-8').replace('/', '_').replace('+', '-')
+
+ def _generate_sid_cookie(self, sid, attributes):
+ """Generate the sid cookie."""
+ cookie = attributes.get('name', 'io') + '=' + sid
+ for attribute, value in attributes.items():
+ if attribute == 'name':
+ continue
+ if callable(value):
+ value = value()
+ if value is True:
+ cookie += '; ' + attribute
+ else:
+ cookie += '; ' + attribute + '=' + value
+ return cookie
- def _handle_connect(self, environ, start_response, transport, b64=False,
+ def _handle_connect(self, environ, start_response, transport,
jsonp_index=None):
"""Handle a client connection request."""
if self.start_service_task:
@@ -493,36 +526,53 @@ class Server(object):
self.start_service_task = False
self.start_background_task(self._service_task)
- sid = self._generate_id()
+ sid = self.generate_id()
s = socket.Socket(self, sid)
self.sockets[sid] = s
- pkt = packet.Packet(
- packet.OPEN, {'sid': sid,
- 'upgrades': self._upgrades(sid, transport),
- 'pingTimeout': int(self.ping_timeout * 1000),
- 'pingInterval': int(self.ping_interval * 1000)})
+ pkt = packet.Packet(packet.OPEN, {
+ 'sid': sid,
+ 'upgrades': self._upgrades(sid, transport),
+ 'pingTimeout': int(self.ping_timeout * 1000),
+ 'pingInterval': int(
+ self.ping_interval + self.ping_interval_grace_period) * 1000})
s.send(pkt)
+ s.schedule_ping()
+ # NOTE: some sections below are marked as "no cover" to workaround
+ # what seems to be a bug in the coverage package. All the lines below
+ # are covered by tests, but some are not reported as such for some
+ # reason
ret = self._trigger_event('connect', sid, environ, run_async=False)
- if ret is False:
+ if ret is not None and ret is not True: # pragma: no cover
del self.sockets[sid]
self.logger.warning('Application rejected connection')
- return self._unauthorized()
+ return self._unauthorized(ret or None)
- if transport == 'websocket':
+ if transport == 'websocket': # pragma: no cover
ret = s.handle_get_request(environ, start_response)
- if s.closed:
+ if s.closed and sid in self.sockets:
# websocket connection ended, so we are done
del self.sockets[sid]
return ret
- else:
+ else: # pragma: no cover
s.connected = True
headers = None
if self.cookie:
- headers = [('Set-Cookie', self.cookie + '=' + sid)]
+ if isinstance(self.cookie, dict):
+ headers = [(
+ 'Set-Cookie',
+ self._generate_sid_cookie(sid, self.cookie)
+ )]
+ else:
+ headers = [(
+ 'Set-Cookie',
+ self._generate_sid_cookie(sid, {
+ 'name': self.cookie, 'path': '/', 'SameSite': 'Lax'
+ })
+ )]
try:
- return self._ok(s.poll(), headers=headers, b64=b64,
+ return self._ok(s.poll(), headers=headers,
jsonp_index=jsonp_index)
except exceptions.QueueEmpty:
return self._bad_request()
@@ -561,29 +611,29 @@ class Server(object):
raise KeyError('Session is disconnected')
return s
- def _ok(self, packets=None, headers=None, b64=False, jsonp_index=None):
+ def _ok(self, packets=None, headers=None, jsonp_index=None):
"""Generate a successful HTTP response."""
if packets is not None:
if headers is None:
headers = []
- if b64:
- headers += [('Content-Type', 'text/plain; charset=UTF-8')]
- else:
- headers += [('Content-Type', 'application/octet-stream')]
+ headers += [('Content-Type', 'text/plain; charset=UTF-8')]
return {'status': '200 OK',
'headers': headers,
'response': payload.Payload(packets=packets).encode(
- b64=b64, jsonp_index=jsonp_index)}
+ jsonp_index=jsonp_index).encode('utf-8')}
else:
return {'status': '200 OK',
'headers': [('Content-Type', 'text/plain')],
'response': b'OK'}
- def _bad_request(self):
+ def _bad_request(self, message=None):
"""Generate a bad request HTTP error response."""
+ if message is None:
+ message = 'Bad Request'
+ message = packet.Packet.json.dumps(message)
return {'status': '400 BAD REQUEST',
'headers': [('Content-Type', 'text/plain')],
- 'response': b'Bad Request'}
+ 'response': message.encode('utf-8')}
def _method_not_found(self):
"""Generate a method not found HTTP error response."""
@@ -591,11 +641,14 @@ class Server(object):
'headers': [('Content-Type', 'text/plain')],
'response': b'Method Not Found'}
- def _unauthorized(self):
+ def _unauthorized(self, message=None):
"""Generate a unauthorized HTTP error response."""
+ if message is None:
+ message = 'Unauthorized'
+ message = packet.Packet.json.dumps(message)
return {'status': '401 UNAUTHORIZED',
- 'headers': [('Content-Type', 'text/plain')],
- 'response': b'Unauthorized'}
+ 'headers': [('Content-Type', 'application/json')],
+ 'response': message.encode('utf-8')}
def _cors_allowed_origins(self, environ):
default_origins = []
@@ -613,7 +666,7 @@ class Server(object):
allowed_origins = default_origins
elif self.cors_allowed_origins == '*':
allowed_origins = None
- elif isinstance(self.cors_allowed_origins, six.string_types):
+ elif isinstance(self.cors_allowed_origins, str):
allowed_origins = [self.cors_allowed_origins]
else:
allowed_origins = self.cors_allowed_origins
@@ -641,7 +694,7 @@ class Server(object):
def _gzip(self, response):
"""Apply gzip compression to a response."""
- bytesio = six.BytesIO()
+ bytesio = io.BytesIO()
with gzip.GzipFile(fileobj=bytesio, mode='w') as gz:
gz.write(response)
return bytesio.getvalue()
@@ -650,6 +703,16 @@ class Server(object):
"""Apply deflate compression to a response."""
return zlib.compress(response)
+ def _log_error_once(self, message, message_key):
+ """Log message with logging.ERROR level the first time, then log
+ with given level."""
+ if message_key not in self.log_message_keys:
+ self.logger.error(message + ' (further occurrences of this error '
+ 'will be logged with level INFO)')
+ self.log_message_keys.add(message_key)
+ else:
+ self.logger.info(message)
+
def _service_task(self): # pragma: no cover
"""Monitor connected clients and clean up those that time out."""
while True:
diff --git a/engineio/socket.py b/engineio/socket.py
index 38593e7..1434b19 100644
--- a/engineio/socket.py
+++ b/engineio/socket.py
@@ -1,4 +1,3 @@
-import six
import sys
import time
@@ -15,11 +14,10 @@ class Socket(object):
self.server = server
self.sid = sid
self.queue = self.server.create_queue()
- self.last_ping = time.time()
+ self.last_ping = None
self.connected = False
self.upgrading = False
self.upgraded = False
- self.packet_backlog = []
self.closing = False
self.closed = False
self.session = {}
@@ -28,7 +26,8 @@ class Socket(object):
"""Wait for packets to send to the client."""
queue_empty = self.server.get_queue_empty_exception()
try:
- packets = [self.queue.get(timeout=self.server.ping_timeout)]
+ packets = [self.queue.get(
+ timeout=self.server.ping_interval + self.server.ping_timeout)]
self.queue.task_done()
except queue_empty:
raise exceptions.QueueEmpty()
@@ -36,8 +35,12 @@ class Socket(object):
return []
while True:
try:
- packets.append(self.queue.get(block=False))
+ pkt = self.queue.get(block=False)
self.queue.task_done()
+ if pkt is None:
+ self.queue.put(None)
+ break
+ packets.append(pkt)
except queue_empty:
break
return packets
@@ -50,9 +53,8 @@ class Socket(object):
self.sid, packet_name,
pkt.data if not isinstance(pkt.data, bytes)
else '<binary>')
- if pkt.packet_type == packet.PING:
- self.last_ping = time.time()
- self.send(packet.Packet(packet.PONG, pkt.data))
+ if pkt.packet_type == packet.PONG:
+ self.schedule_ping()
elif pkt.packet_type == packet.MESSAGE:
self.server._trigger_event('message', self.sid, pkt.data,
run_async=self.server.async_handlers)
@@ -64,14 +66,11 @@ class Socket(object):
raise exceptions.UnknownPacketError()
def check_ping_timeout(self):
- """Make sure the client is still sending pings.
-
- This helps detect disconnections for long-polling clients.
- """
+ """Make sure the client is still responding to pings."""
if self.closed:
raise exceptions.SocketIsClosedError()
- if time.time() - self.last_ping > self.server.ping_interval + \
- self.server.ping_interval_grace_period:
+ if self.last_ping and \
+ time.time() - self.last_ping > self.server.ping_timeout:
self.server.logger.info('%s: Client is gone, closing socket',
self.sid)
# Passing abort=False here will cause close() to write a
@@ -85,8 +84,6 @@ class Socket(object):
"""Send a packet to the client."""
if not self.check_ping_timeout():
return
- if self.upgrading:
- self.packet_backlog.append(pkt)
else:
self.queue.put(pkt)
self.server.logger.info('%s: Sending packet %s data %s',
@@ -105,12 +102,16 @@ class Socket(object):
self.sid, transport)
return getattr(self, '_upgrade_' + transport)(environ,
start_response)
+ if self.upgrading or self.upgraded:
+ # we are upgrading to WebSocket, do not return any more packets
+ # through the polling endpoint
+ return [packet.Packet(packet.NOOP)]
try:
packets = self.poll()
except exceptions.QueueEmpty:
exc = sys.exc_info()
self.close(wait=False)
- six.reraise(*exc)
+ raise exc[1].with_traceback(exc[2])
return packets
def handle_post_request(self, environ):
@@ -119,7 +120,7 @@ class Socket(object):
if length > self.server.max_http_buffer_size:
raise exceptions.ContentTooLongError()
else:
- body = environ['wsgi.input'].read(length)
+ body = environ['wsgi.input'].read(length).decode('utf-8')
p = payload.Payload(encoded_payload=body)
for pkt in p.packets:
self.receive(pkt)
@@ -136,6 +137,16 @@ class Socket(object):
if wait:
self.queue.join()
+ def schedule_ping(self):
+ def send_ping():
+ self.last_ping = None
+ self.server.sleep(self.server.ping_interval)
+ if not self.closing and not self.closed:
+ self.last_ping = time.time()
+ self.send(packet.Packet(packet.PING))
+
+ self.server.start_background_task(send_ping)
+
def _upgrade_websocket(self, environ, start_response):
"""Upgrade the connection from polling to websocket."""
if self.upgraded:
@@ -149,9 +160,11 @@ class Socket(object):
def _websocket_handler(self, ws):
"""Engine.IO handler for websocket transport."""
# try to set a socket timeout matching the configured ping interval
+ # and timeout
for attr in ['_sock', 'socket']: # pragma: no cover
if hasattr(ws, attr) and hasattr(getattr(ws, attr), 'settimeout'):
- getattr(ws, attr).settimeout(self.server.ping_timeout)
+ getattr(ws, attr).settimeout(
+ self.server.ping_interval + self.server.ping_timeout)
if self.connected:
# the socket was already connected, so this is an upgrade
@@ -163,10 +176,9 @@ class Socket(object):
decoded_pkt.data != 'probe':
self.server.logger.info(
'%s: Failed websocket upgrade, no PING packet', self.sid)
+ self.upgrading = False
return []
- ws.send(packet.Packet(
- packet.PONG,
- data=six.text_type('probe')).encode(always_bytes=False))
+ ws.send(packet.Packet(packet.PONG, data='probe').encode())
self.queue.put(packet.Packet(packet.NOOP)) # end poll
pkt = ws.wait()
@@ -177,13 +189,9 @@ class Socket(object):
('%s: Failed websocket upgrade, expected UPGRADE packet, '
'received %s instead.'),
self.sid, pkt)
+ self.upgrading = False
return []
self.upgraded = True
-
- # flush any packets that were sent during the upgrade
- for pkt in self.packet_backlog:
- self.queue.put(pkt)
- self.packet_backlog = []
self.upgrading = False
else:
self.connected = True
@@ -202,7 +210,7 @@ class Socket(object):
break
try:
for pkt in packets:
- ws.send(pkt.encode(always_bytes=False))
+ ws.send(pkt.encode())
except:
break
writer_task = self.server.start_background_task(writer)
@@ -225,8 +233,6 @@ class Socket(object):
if p is None:
# connection closed by client
break
- if isinstance(p, six.text_type): # pragma: no cover
- p = p.encode('utf-8')
pkt = packet.Packet(encoded_packet=p)
try:
self.receive(pkt)
diff --git a/python_engineio.egg-info/PKG-INFO b/python_engineio.egg-info/PKG-INFO
index d913b64..4aca5ae 100644
--- a/python_engineio.egg-info/PKG-INFO
+++ b/python_engineio.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: python-engineio
-Version: 3.11.1
+Version: 4.0.0
Summary: Engine.IO server
Home-page: http://github.com/miguelgrinberg/python-engineio/
Author: Miguel Grinberg
@@ -9,8 +9,11 @@ License: MIT
Description: python-engineio
===============
- .. image:: https://travis-ci.org/miguelgrinberg/python-engineio.svg?branch=master
- :target: https://travis-ci.org/miguelgrinberg/python-engineio
+ .. image:: https://github.com/miguelgrinberg/python-engineio/workflows/build/badge.svg
+ :target: https://github.com/miguelgrinberg/python-engineio/actions
+
+ .. image:: https://codecov.io/gh/miguelgrinberg/python-engineio/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/miguelgrinberg/python-engineio
Python implementation of the `Engine.IO`_ realtime client and server.
@@ -39,5 +42,5 @@ Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Provides-Extra: asyncio_client
Provides-Extra: client
+Provides-Extra: asyncio_client
diff --git a/python_engineio.egg-info/requires.txt b/python_engineio.egg-info/requires.txt
index 44b827e..bea152f 100644
--- a/python_engineio.egg-info/requires.txt
+++ b/python_engineio.egg-info/requires.txt
@@ -1,4 +1,3 @@
-six>=1.9.0
[asyncio_client]
aiohttp>=3.4
diff --git a/setup.py b/setup.py
index c77399c..b9f5271 100755
--- a/setup.py
+++ b/setup.py
@@ -29,9 +29,7 @@ setup(
zip_safe=False,
include_package_data=True,
platforms='any',
- install_requires=[
- 'six>=1.9.0',
- ],
+ install_requires=[],
extras_require={
'client': [
'requests>=2.21.0',