Code import
This commit is contained in:
14
venv/lib/python2.7/site-packages/engineio/__init__.py
Normal file
14
venv/lib/python2.7/site-packages/engineio/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
|
||||
from .middleware import Middleware
|
||||
from .server import Server
|
||||
if sys.version_info >= (3, 5): # pragma: no cover
|
||||
from .asyncio_server import AsyncServer
|
||||
else: # pragma: no cover
|
||||
AsyncServer = None
|
||||
|
||||
__version__ = '1.7.0'
|
||||
|
||||
__all__ = ['__version__', 'Middleware', 'Server']
|
||||
if AsyncServer is not None: # pragma: no cover
|
||||
__all__.append('AsyncServer')
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/__init__.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/__init__.pyc
Normal file
Binary file not shown.
122
venv/lib/python2.7/site-packages/engineio/async_aiohttp.py
Normal file
122
venv/lib/python2.7/site-packages/engineio/async_aiohttp.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import sys
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import aiohttp
|
||||
import six
|
||||
|
||||
|
||||
def create_route(app, engineio_server, engineio_endpoint):
|
||||
"""This function sets up the engine.io endpoint as a route for the
|
||||
application.
|
||||
|
||||
Note that both GET and POST requests must be hooked up on the engine.io
|
||||
endpoint.
|
||||
"""
|
||||
app.router.add_get(engineio_endpoint, engineio_server.handle_request)
|
||||
app.router.add_post(engineio_endpoint, engineio_server.handle_request)
|
||||
|
||||
|
||||
def translate_request(request):
|
||||
"""This function takes the arguments passed to the request handler and
|
||||
uses them to generate a WSGI compatible environ dictionary.
|
||||
"""
|
||||
message = request._message
|
||||
payload = request._payload
|
||||
|
||||
uri_parts = urlsplit(message.path)
|
||||
environ = {
|
||||
'wsgi.input': payload,
|
||||
'wsgi.errors': sys.stderr,
|
||||
'wsgi.version': (1, 0),
|
||||
'wsgi.async': True,
|
||||
'wsgi.multithread': False,
|
||||
'wsgi.multiprocess': False,
|
||||
'wsgi.run_once': False,
|
||||
'SERVER_SOFTWARE': 'aiohttp',
|
||||
'REQUEST_METHOD': message.method,
|
||||
'QUERY_STRING': uri_parts.query or '',
|
||||
'RAW_URI': message.path,
|
||||
'SERVER_PROTOCOL': 'HTTP/%s.%s' % message.version,
|
||||
'REMOTE_ADDR': '127.0.0.1',
|
||||
'REMOTE_PORT': '0',
|
||||
'SERVER_NAME': 'aiohttp',
|
||||
'SERVER_PORT': '0',
|
||||
'aiohttp.request': request
|
||||
}
|
||||
|
||||
for hdr_name, hdr_value in message.headers.items():
|
||||
hdr_name = hdr_name.upper()
|
||||
if hdr_name == 'CONTENT-TYPE':
|
||||
environ['CONTENT_TYPE'] = hdr_value
|
||||
continue
|
||||
elif hdr_name == 'CONTENT-LENGTH':
|
||||
environ['CONTENT_LENGTH'] = hdr_value
|
||||
continue
|
||||
|
||||
key = 'HTTP_%s' % hdr_name.replace('-', '_')
|
||||
if key in environ:
|
||||
hdr_value = '%s,%s' % (environ[key], hdr_value)
|
||||
|
||||
environ[key] = hdr_value
|
||||
|
||||
environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http')
|
||||
|
||||
path_info = uri_parts.path
|
||||
|
||||
environ['PATH_INFO'] = path_info
|
||||
environ['SCRIPT_NAME'] = ''
|
||||
|
||||
return environ
|
||||
|
||||
|
||||
def make_response(status, headers, payload):
|
||||
"""This function generates an appropriate response object for this async
|
||||
mode.
|
||||
"""
|
||||
return aiohttp.web.Response(body=payload, status=int(status.split()[0]),
|
||||
headers=headers)
|
||||
|
||||
|
||||
class WebSocket(object): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides a aiohttp WebSocket interface that is
|
||||
somewhat compatible with eventlet's implementation.
|
||||
"""
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
self._sock = None
|
||||
|
||||
async def __call__(self, environ):
|
||||
request = environ['aiohttp.request']
|
||||
self._sock = aiohttp.web.WebSocketResponse()
|
||||
await self._sock.prepare(request)
|
||||
|
||||
self.environ = environ
|
||||
await self.handler(self)
|
||||
return self._sock
|
||||
|
||||
async def close(self):
|
||||
await self._sock.close()
|
||||
|
||||
async def send(self, message):
|
||||
if isinstance(message, bytes):
|
||||
self._sock.send_bytes(message)
|
||||
else:
|
||||
self._sock.send_str(message)
|
||||
|
||||
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):
|
||||
raise IOError()
|
||||
return msg.data
|
||||
|
||||
|
||||
_async = {
|
||||
'asyncio': True,
|
||||
'create_route': create_route,
|
||||
'translate_request': translate_request,
|
||||
'make_response': make_response,
|
||||
'websocket': sys.modules[__name__],
|
||||
'websocket_class': 'WebSocket'
|
||||
}
|
||||
30
venv/lib/python2.7/site-packages/engineio/async_eventlet.py
Normal file
30
venv/lib/python2.7/site-packages/engineio/async_eventlet.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
from eventlet import sleep
|
||||
from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI
|
||||
|
||||
|
||||
class WebSocketWSGI(_WebSocketWSGI):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WebSocketWSGI, self).__init__(*args, **kwargs)
|
||||
self._sock = None
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
if 'eventlet.input' not in environ:
|
||||
raise RuntimeError('You need to use the eventlet server. '
|
||||
'See the Deployment section of the '
|
||||
'documentation for more information.')
|
||||
self._sock = environ['eventlet.input'].get_socket()
|
||||
return super(WebSocketWSGI, self).__call__(environ, start_response)
|
||||
|
||||
|
||||
_async = {
|
||||
'threading': importlib.import_module('eventlet.green.threading'),
|
||||
'thread_class': 'Thread',
|
||||
'queue': importlib.import_module('eventlet.queue'),
|
||||
'queue_class': 'Queue',
|
||||
'websocket': sys.modules[__name__],
|
||||
'websocket_class': 'WebSocketWSGI',
|
||||
'sleep': sleep
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/async_eventlet.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/async_eventlet.pyc
Normal file
Binary file not shown.
63
venv/lib/python2.7/site-packages/engineio/async_gevent.py
Normal file
63
venv/lib/python2.7/site-packages/engineio/async_gevent.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
import gevent
|
||||
try:
|
||||
import geventwebsocket # noqa
|
||||
_websocket_available = True
|
||||
except ImportError:
|
||||
_websocket_available = False
|
||||
|
||||
|
||||
class Thread(gevent.Greenlet): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides gevent Greenlet interface that is compatible
|
||||
with the standard library's Thread class.
|
||||
"""
|
||||
def __init__(self, target, args=[], kwargs={}):
|
||||
super(Thread, self).__init__(target, *args, **kwargs)
|
||||
|
||||
def _run(self):
|
||||
return self.run()
|
||||
|
||||
|
||||
class WebSocketWSGI(object): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides a gevent WebSocket interface that is
|
||||
compatible with eventlet's implementation.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
if 'wsgi.websocket' not in environ:
|
||||
raise RuntimeError('You need to use the gevent-websocket server. '
|
||||
'See the Deployment section of the '
|
||||
'documentation for more information.')
|
||||
self._sock = environ['wsgi.websocket']
|
||||
self.environ = environ
|
||||
self.version = self._sock.version
|
||||
self.path = self._sock.path
|
||||
self.origin = self._sock.origin
|
||||
self.protocol = self._sock.protocol
|
||||
return self.app(self)
|
||||
|
||||
def close(self):
|
||||
return self._sock.close()
|
||||
|
||||
def send(self, message):
|
||||
return self._sock.send(message)
|
||||
|
||||
def wait(self):
|
||||
return self._sock.receive()
|
||||
|
||||
|
||||
_async = {
|
||||
'threading': sys.modules[__name__],
|
||||
'thread_class': 'Thread',
|
||||
'queue': importlib.import_module('gevent.queue'),
|
||||
'queue_class': 'JoinableQueue',
|
||||
'websocket': sys.modules[__name__] if _websocket_available else None,
|
||||
'websocket_class': 'WebSocketWSGI' if _websocket_available else None,
|
||||
'sleep': gevent.sleep
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/async_gevent.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/async_gevent.pyc
Normal file
Binary file not shown.
155
venv/lib/python2.7/site-packages/engineio/async_gevent_uwsgi.py
Normal file
155
venv/lib/python2.7/site-packages/engineio/async_gevent_uwsgi.py
Normal file
@@ -0,0 +1,155 @@
|
||||
import importlib
|
||||
import sys
|
||||
import six
|
||||
|
||||
import gevent
|
||||
import uwsgi
|
||||
_websocket_available = hasattr(uwsgi, 'websocket_handshake')
|
||||
|
||||
|
||||
class Thread(gevent.Greenlet): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides gevent Greenlet interface that is compatible
|
||||
with the standard library's Thread class.
|
||||
"""
|
||||
def __init__(self, target, args=[], kwargs={}):
|
||||
super(Thread, self).__init__(target, *args, **kwargs)
|
||||
|
||||
def _run(self):
|
||||
return self.run()
|
||||
|
||||
|
||||
class uWSGIWebSocket(object): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides a uWSGI WebSocket interface that is
|
||||
compatible with eventlet's implementation.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self._sock = None
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
self._sock = uwsgi.connection_fd()
|
||||
self.environ = environ
|
||||
|
||||
uwsgi.websocket_handshake()
|
||||
|
||||
self._req_ctx = None
|
||||
if hasattr(uwsgi, 'request_context'):
|
||||
# uWSGI >= 2.1.x with support for api access across-greenlets
|
||||
self._req_ctx = uwsgi.request_context()
|
||||
else:
|
||||
# use event and queue for sending messages
|
||||
from gevent.event import Event
|
||||
from gevent.queue import Queue
|
||||
from gevent.select import select
|
||||
self._event = Event()
|
||||
self._send_queue = Queue()
|
||||
|
||||
# spawn a select greenlet
|
||||
def select_greenlet_runner(fd, event):
|
||||
"""Sets event when data becomes available to read on fd."""
|
||||
while True:
|
||||
event.set()
|
||||
try:
|
||||
select([fd], [], [])[0]
|
||||
except ValueError:
|
||||
break
|
||||
self._select_greenlet = gevent.spawn(
|
||||
select_greenlet_runner,
|
||||
self._sock,
|
||||
self._event)
|
||||
|
||||
self.app(self)
|
||||
|
||||
def close(self):
|
||||
"""Disconnects uWSGI from the client."""
|
||||
uwsgi.disconnect()
|
||||
if self._req_ctx is None:
|
||||
# better kill it here in case wait() is not called again
|
||||
self._select_greenlet.kill()
|
||||
self._event.set()
|
||||
|
||||
def _send(self, msg):
|
||||
"""Transmits message either in binary or UTF-8 text mode,
|
||||
depending on its type."""
|
||||
if isinstance(msg, six.binary_type):
|
||||
method = uwsgi.websocket_send_binary
|
||||
else:
|
||||
method = uwsgi.websocket_send
|
||||
if self._req_ctx is not None:
|
||||
method(msg, request_context=self._req_ctx)
|
||||
else:
|
||||
method(msg)
|
||||
|
||||
def _decode_received(self, msg):
|
||||
"""Returns either bytes or str, depending on message type."""
|
||||
if not isinstance(msg, six.binary_type):
|
||||
# already decoded - do nothing
|
||||
return msg
|
||||
# only decode from utf-8 if message is not binary data
|
||||
type = six.byte2int(msg[0:1])
|
||||
if type >= 48: # no binary
|
||||
return msg.decode('utf-8')
|
||||
# binary message, don't try to decode
|
||||
return msg
|
||||
|
||||
def send(self, msg):
|
||||
"""Queues a message for sending. Real transmission is done in
|
||||
wait method.
|
||||
Sends directly if uWSGI version is new enough."""
|
||||
if self._req_ctx is not None:
|
||||
self._send(msg)
|
||||
else:
|
||||
self._send_queue.put(msg)
|
||||
self._event.set()
|
||||
|
||||
def wait(self):
|
||||
"""Waits and returns received messages.
|
||||
If running in compatibility mode for older uWSGI versions,
|
||||
it also sends messages that have been queued by send().
|
||||
A return value of None means that connection was closed.
|
||||
This must be called repeatedly. For uWSGI < 2.1.x it must
|
||||
be called from the main greenlet."""
|
||||
while True:
|
||||
if self._req_ctx is not None:
|
||||
try:
|
||||
msg = uwsgi.websocket_recv(request_context=self._req_ctx)
|
||||
except IOError: # connection closed
|
||||
return None
|
||||
return self._decode_received(msg)
|
||||
else:
|
||||
# we wake up at least every 3 seconds to let uWSGI
|
||||
# do its ping/ponging
|
||||
event_set = self._event.wait(timeout=3)
|
||||
if event_set:
|
||||
self._event.clear()
|
||||
# maybe there is something to send
|
||||
msgs = []
|
||||
while True:
|
||||
try:
|
||||
msgs.append(self._send_queue.get(block=False))
|
||||
except gevent.queue.Empty:
|
||||
break
|
||||
for msg in msgs:
|
||||
self._send(msg)
|
||||
# maybe there is something to receive, if not, at least
|
||||
# ensure uWSGI does its ping/ponging
|
||||
try:
|
||||
msg = uwsgi.websocket_recv_nb()
|
||||
except IOError: # connection closed
|
||||
self._select_greenlet.kill()
|
||||
return None
|
||||
if msg: # message available
|
||||
return self._decode_received(msg)
|
||||
|
||||
|
||||
_async = {
|
||||
'threading': sys.modules[__name__],
|
||||
'thread_class': 'Thread',
|
||||
'queue': importlib.import_module('gevent.queue'),
|
||||
'queue_class': 'JoinableQueue',
|
||||
'websocket': sys.modules[__name__] if _websocket_available else None,
|
||||
'websocket_class': 'uWSGIWebSocket' if _websocket_available else None,
|
||||
'sleep': gevent.sleep
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/async_gevent_uwsgi.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/async_gevent_uwsgi.pyc
Normal file
Binary file not shown.
145
venv/lib/python2.7/site-packages/engineio/async_sanic.py
Normal file
145
venv/lib/python2.7/site-packages/engineio/async_sanic.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import sys
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from sanic.response import HTTPResponse
|
||||
try:
|
||||
from sanic.websocket import WebSocketProtocol
|
||||
except ImportError:
|
||||
# the installed version of sanic does not have websocket support
|
||||
WebSocketProtocol = None
|
||||
import six
|
||||
|
||||
|
||||
def create_route(app, engineio_server, engineio_endpoint):
|
||||
"""This function sets up the engine.io endpoint as a route for the
|
||||
application.
|
||||
|
||||
Note that both GET and POST requests must be hooked up on the engine.io
|
||||
endpoint.
|
||||
"""
|
||||
app.add_route(engineio_server.handle_request, engineio_endpoint,
|
||||
methods=['GET', 'POST'])
|
||||
try:
|
||||
app.enable_websocket()
|
||||
except AttributeError:
|
||||
# ignore, this version does not support websocket
|
||||
pass
|
||||
|
||||
|
||||
def translate_request(request):
|
||||
"""This function takes the arguments passed to the request handler and
|
||||
uses them to generate a WSGI compatible environ dictionary.
|
||||
"""
|
||||
class AwaitablePayload(object):
|
||||
def __init__(self, payload):
|
||||
self.payload = payload or b''
|
||||
|
||||
async def read(self, length=None):
|
||||
if length is None:
|
||||
r = self.payload
|
||||
self.payload = b''
|
||||
else:
|
||||
r = self.payload[:length]
|
||||
self.payload = self.payload[length:]
|
||||
return r
|
||||
|
||||
uri_parts = urlsplit(request.url)
|
||||
environ = {
|
||||
'wsgi.input': AwaitablePayload(request.body),
|
||||
'wsgi.errors': sys.stderr,
|
||||
'wsgi.version': (1, 0),
|
||||
'wsgi.async': True,
|
||||
'wsgi.multithread': False,
|
||||
'wsgi.multiprocess': False,
|
||||
'wsgi.run_once': False,
|
||||
'SERVER_SOFTWARE': 'sanic',
|
||||
'REQUEST_METHOD': request.method,
|
||||
'QUERY_STRING': uri_parts.query or '',
|
||||
'RAW_URI': request.url,
|
||||
'SERVER_PROTOCOL': 'HTTP/' + request.version,
|
||||
'REMOTE_ADDR': '127.0.0.1',
|
||||
'REMOTE_PORT': '0',
|
||||
'SERVER_NAME': 'sanic',
|
||||
'SERVER_PORT': '0',
|
||||
'sanic.request': request
|
||||
}
|
||||
|
||||
for hdr_name, hdr_value in request.headers.items():
|
||||
hdr_name = hdr_name.upper()
|
||||
if hdr_name == 'CONTENT-TYPE':
|
||||
environ['CONTENT_TYPE'] = hdr_value
|
||||
continue
|
||||
elif hdr_name == 'CONTENT-LENGTH':
|
||||
environ['CONTENT_LENGTH'] = hdr_value
|
||||
continue
|
||||
|
||||
key = 'HTTP_%s' % hdr_name.replace('-', '_')
|
||||
if key in environ:
|
||||
hdr_value = '%s,%s' % (environ[key], hdr_value)
|
||||
|
||||
environ[key] = hdr_value
|
||||
|
||||
environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http')
|
||||
|
||||
path_info = uri_parts.path
|
||||
|
||||
environ['PATH_INFO'] = path_info
|
||||
environ['SCRIPT_NAME'] = ''
|
||||
|
||||
return environ
|
||||
|
||||
|
||||
def make_response(status, headers, payload):
|
||||
"""This function generates an appropriate response object for this async
|
||||
mode.
|
||||
"""
|
||||
headers_dict = {}
|
||||
content_type = None
|
||||
for h in headers:
|
||||
if h[0].lower() == 'content-type':
|
||||
content_type = h[1]
|
||||
else:
|
||||
headers_dict[h[0]] = h[1]
|
||||
return HTTPResponse(body_bytes=payload, content_type=content_type,
|
||||
status=int(status.split()[0]), headers=headers_dict)
|
||||
|
||||
|
||||
class WebSocket(object): # pragma: no cover
|
||||
"""
|
||||
This wrapper class provides a sanic WebSocket interface that is
|
||||
somewhat compatible with eventlet's implementation.
|
||||
"""
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
self._sock = None
|
||||
|
||||
async def __call__(self, environ):
|
||||
request = environ['sanic.request']
|
||||
protocol = request.transport.get_protocol()
|
||||
self._sock = await protocol.websocket_handshake(request)
|
||||
|
||||
self.environ = environ
|
||||
await self.handler(self)
|
||||
|
||||
async def close(self):
|
||||
await self._sock.close()
|
||||
|
||||
async def send(self, message):
|
||||
await self._sock.send(message)
|
||||
|
||||
async def wait(self):
|
||||
data = await self._sock.recv()
|
||||
if not isinstance(data, six.binary_type) and \
|
||||
not isinstance(data, six.text_type):
|
||||
raise IOError()
|
||||
return data
|
||||
|
||||
|
||||
_async = {
|
||||
'asyncio': True,
|
||||
'create_route': create_route,
|
||||
'translate_request': translate_request,
|
||||
'make_response': make_response,
|
||||
'websocket': sys.modules[__name__] if WebSocketProtocol else None,
|
||||
'websocket_class': 'WebSocket' if WebSocketProtocol else None
|
||||
}
|
||||
17
venv/lib/python2.7/site-packages/engineio/async_threading.py
Normal file
17
venv/lib/python2.7/site-packages/engineio/async_threading.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import importlib
|
||||
import time
|
||||
|
||||
try:
|
||||
queue = importlib.import_module('queue')
|
||||
except ImportError: # pragma: no cover
|
||||
queue = importlib.import_module('Queue') # pragma: no cover
|
||||
|
||||
_async = {
|
||||
'threading': importlib.import_module('threading'),
|
||||
'thread_class': 'Thread',
|
||||
'queue': queue,
|
||||
'queue_class': 'Queue',
|
||||
'websocket': None,
|
||||
'websocket_class': None,
|
||||
'sleep': time.sleep
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/async_threading.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/async_threading.pyc
Normal file
Binary file not shown.
290
venv/lib/python2.7/site-packages/engineio/asyncio_server.py
Normal file
290
venv/lib/python2.7/site-packages/engineio/asyncio_server.py
Normal file
@@ -0,0 +1,290 @@
|
||||
import asyncio
|
||||
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from .exceptions import EngineIOError
|
||||
from . import packet
|
||||
from . import server
|
||||
from . import asyncio_socket
|
||||
|
||||
|
||||
class AsyncServer(server.Server):
|
||||
"""An Engine.IO server for asyncio.
|
||||
|
||||
This class implements a fully compliant Engine.IO web server with support
|
||||
for websocket and long-polling transports, compatible with the asyncio
|
||||
framework on Python 3.5 or newer.
|
||||
|
||||
:param async_mode: The asynchronous model to use. See the Deployment
|
||||
section in the documentation for a description of the
|
||||
available options. Valid async modes are "aiohttp". If
|
||||
this argument is not given, an async mode is chosen
|
||||
based on the installed packages.
|
||||
: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.
|
||||
:param max_http_buffer_size: The maximum size of a message when using the
|
||||
polling transport.
|
||||
: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 cors_allowed_origins: List of origins that are allowed to connect
|
||||
to this server. All origins are allowed by
|
||||
default.
|
||||
: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``.
|
||||
: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 async_handlers: If set to ``True``, run message event handlers in
|
||||
non-blocking threads. To run handlers synchronously,
|
||||
set to ``False``. The default is ``True``.
|
||||
:param kwargs: Reserved for future extensions, any additional parameters
|
||||
given as keyword arguments will be silently ignored.
|
||||
"""
|
||||
def is_asyncio_based(self):
|
||||
return True
|
||||
|
||||
def async_modes(self):
|
||||
return ['aiohttp', 'sanic']
|
||||
|
||||
def attach(self, app, engineio_path='engine.io'):
|
||||
"""Attach the Engine.IO server to an application."""
|
||||
engineio_path = engineio_path.strip('/')
|
||||
self._async['create_route'](app, self, '/{}/'.format(engineio_path))
|
||||
|
||||
async def send(self, sid, data, binary=None):
|
||||
"""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.
|
||||
"""
|
||||
try:
|
||||
socket = self._get_socket(sid)
|
||||
except KeyError:
|
||||
# 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))
|
||||
|
||||
async def disconnect(self, sid=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
:param sid: The session id of the client to close. If this parameter
|
||||
is not given, then all clients are closed.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
if sid is not None:
|
||||
try:
|
||||
socket = self._get_socket(sid)
|
||||
except KeyError: # pragma: no cover
|
||||
# the socket was already closed or gone
|
||||
pass
|
||||
else:
|
||||
await socket.close()
|
||||
del self.sockets[sid]
|
||||
else:
|
||||
await asyncio.wait([client.close()
|
||||
for client in six.itervalues(self.sockets)])
|
||||
self.sockets = {}
|
||||
|
||||
async def handle_request(self, *args, **kwargs):
|
||||
"""Handle an HTTP request from the client.
|
||||
|
||||
This is the entry point of the Engine.IO application. This function
|
||||
returns the HTTP response to deliver to the client.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
environ = self._async['translate_request'](*args, **kwargs)
|
||||
method = environ['REQUEST_METHOD']
|
||||
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', ''))
|
||||
if 'j' in query:
|
||||
self.logger.warning('JSONP requests are not supported')
|
||||
r = self._bad_request()
|
||||
else:
|
||||
sid = query['sid'][0] if 'sid' in query else None
|
||||
b64 = False
|
||||
if 'b64' in query:
|
||||
if query['b64'][0] == "1" or query['b64'][0].lower() == "true":
|
||||
b64 = True
|
||||
if 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:
|
||||
r = await self._handle_connect(environ, transport,
|
||||
b64)
|
||||
else:
|
||||
if sid not in self.sockets:
|
||||
self.logger.warning('Invalid session %s', sid)
|
||||
r = self._bad_request()
|
||||
else:
|
||||
socket = self._get_socket(sid)
|
||||
try:
|
||||
packets = await socket.handle_get_request(environ)
|
||||
if isinstance(packets, list):
|
||||
r = self._ok(packets, b64=b64)
|
||||
else:
|
||||
r = packets
|
||||
except EngineIOError:
|
||||
if sid in self.sockets: # pragma: no cover
|
||||
await self.disconnect(sid)
|
||||
r = self._bad_request()
|
||||
if sid in self.sockets and self.sockets[sid].closed:
|
||||
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()
|
||||
else:
|
||||
socket = self._get_socket(sid)
|
||||
try:
|
||||
await socket.handle_post_request(environ)
|
||||
r = self._ok()
|
||||
except EngineIOError:
|
||||
if sid in self.sockets: # pragma: no cover
|
||||
await self.disconnect(sid)
|
||||
r = self._bad_request()
|
||||
except: # pragma: no cover
|
||||
# for any other unexpected errors, we log the error
|
||||
# and keep going
|
||||
self.logger.exception('post request handler error')
|
||||
r = self._ok()
|
||||
else:
|
||||
self.logger.warning('Method %s not supported', method)
|
||||
r = self._method_not_found()
|
||||
if not isinstance(r, dict):
|
||||
return r or []
|
||||
if self.http_compression and \
|
||||
len(r['response']) >= self.compression_threshold:
|
||||
encodings = [e.split(';')[0].strip() for e in
|
||||
environ.get('HTTP_ACCEPT_ENCODING', '').split(',')]
|
||||
for encoding in encodings:
|
||||
if encoding in self.compression_methods:
|
||||
r['response'] = \
|
||||
getattr(self, '_' + encoding)(r['response'])
|
||||
r['headers'] += [('Content-Encoding', encoding)]
|
||||
break
|
||||
cors_headers = self._cors_headers(environ)
|
||||
return self._async['make_response'](r['status'],
|
||||
r['headers'] + cors_headers,
|
||||
r['response'])
|
||||
|
||||
def start_background_task(self, target, *args, **kwargs):
|
||||
"""Start a background task using the appropriate async model.
|
||||
|
||||
This is a utility function that applications can use to start a
|
||||
background task using the method that is compatible with the
|
||||
selected async mode.
|
||||
|
||||
:param target: the target function to execute.
|
||||
:param args: arguments to pass to the function.
|
||||
:param kwargs: keyword arguments to pass to the function.
|
||||
|
||||
The return value is a ``asyncio.Task`` object.
|
||||
"""
|
||||
return asyncio.ensure_future(target(*args, **kwargs))
|
||||
|
||||
async def sleep(self, seconds=0):
|
||||
"""Sleep for the requested amount of time using the appropriate async
|
||||
model.
|
||||
|
||||
This is a utility function that applications can use to put a task to
|
||||
sleep without having to worry about using the correct call for the
|
||||
selected async mode.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await asyncio.sleep(seconds)
|
||||
|
||||
async def _handle_connect(self, environ, transport, b64=False):
|
||||
"""Handle a client connection request."""
|
||||
sid = self._generate_id()
|
||||
s = asyncio_socket.AsyncSocket(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)})
|
||||
await s.send(pkt)
|
||||
|
||||
ret = await self._trigger_event('connect', sid, environ,
|
||||
run_async=False)
|
||||
if ret is False:
|
||||
del self.sockets[sid]
|
||||
self.logger.warning('Application rejected connection')
|
||||
return self._unauthorized()
|
||||
|
||||
if transport == 'websocket':
|
||||
ret = await s.handle_get_request(environ)
|
||||
if s.closed:
|
||||
# websocket connection ended, so we are done
|
||||
del self.sockets[sid]
|
||||
return ret
|
||||
else:
|
||||
s.connected = True
|
||||
headers = None
|
||||
if self.cookie:
|
||||
headers = [('Set-Cookie', self.cookie + '=' + sid)]
|
||||
return self._ok(await s.poll(), headers=headers, b64=b64)
|
||||
|
||||
async def _trigger_event(self, event, *args, **kwargs):
|
||||
"""Invoke an event handler."""
|
||||
run_async = kwargs.pop('run_async', False)
|
||||
ret = None
|
||||
if event in self.handlers:
|
||||
if asyncio.iscoroutinefunction(self.handlers[event]) is True:
|
||||
if run_async:
|
||||
return self.start_background_task(self.handlers[event],
|
||||
*args)
|
||||
else:
|
||||
try:
|
||||
ret = await self.handlers[event](*args)
|
||||
except asyncio.CancelledError: # pragma: no cover
|
||||
pass
|
||||
except:
|
||||
self.logger.exception(event + ' async handler error')
|
||||
if event == 'connect':
|
||||
# if connect handler raised error we reject the
|
||||
# connection
|
||||
return False
|
||||
else:
|
||||
if run_async:
|
||||
async def async_handler():
|
||||
return self.handlers[event](*args)
|
||||
|
||||
return self.start_background_task(async_handler)
|
||||
else:
|
||||
try:
|
||||
ret = self.handlers[event](*args)
|
||||
except:
|
||||
self.logger.exception(event + ' handler error')
|
||||
if event == 'connect':
|
||||
# if connect handler raised error we reject the
|
||||
# connection
|
||||
return False
|
||||
return ret
|
||||
208
venv/lib/python2.7/site-packages/engineio/asyncio_socket.py
Normal file
208
venv/lib/python2.7/site-packages/engineio/asyncio_socket.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import asyncio
|
||||
import six
|
||||
import time
|
||||
|
||||
from . import exceptions
|
||||
from . import packet
|
||||
from . import payload
|
||||
from . import socket
|
||||
|
||||
|
||||
class AsyncSocket(socket.Socket):
|
||||
def create_queue(self):
|
||||
return asyncio.Queue()
|
||||
|
||||
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)]
|
||||
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
|
||||
return packets
|
||||
|
||||
async def receive(self, pkt):
|
||||
"""Receive packet from the client."""
|
||||
self.server.logger.info('%s: Received packet %s data %s',
|
||||
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))
|
||||
elif pkt.packet_type == packet.MESSAGE:
|
||||
await self.server._trigger_event(
|
||||
'message', self.sid, pkt.data,
|
||||
run_async=self.server.async_handlers)
|
||||
elif pkt.packet_type == packet.UPGRADE:
|
||||
await self.send(packet.Packet(packet.NOOP))
|
||||
elif pkt.packet_type == packet.CLOSE:
|
||||
await self.close(wait=False, abort=True)
|
||||
else:
|
||||
raise exceptions.UnknownPacketError()
|
||||
|
||||
async def send(self, pkt):
|
||||
"""Send a packet to the client."""
|
||||
if self.closed:
|
||||
raise IOError('Socket is closed')
|
||||
if time.time() - self.last_ping > self.server.ping_timeout:
|
||||
self.server.logger.info('%s: Client is gone, closing socket',
|
||||
self.sid)
|
||||
return await self.close(wait=False, abort=True)
|
||||
self.server.logger.info('%s: Sending packet %s data %s',
|
||||
self.sid, packet.packet_names[pkt.packet_type],
|
||||
pkt.data if not isinstance(pkt.data, bytes)
|
||||
else '<binary>')
|
||||
await self.queue.put(pkt)
|
||||
|
||||
async def handle_get_request(self, environ):
|
||||
"""Handle a long-polling GET request from the client."""
|
||||
connections = [
|
||||
s.strip()
|
||||
for s in environ.get('HTTP_CONNECTION', '').lower().split(',')]
|
||||
transport = environ.get('HTTP_UPGRADE', '').lower()
|
||||
if 'upgrade' in connections and transport in self.upgrade_protocols:
|
||||
self.server.logger.info('%s: Received request to upgrade to %s',
|
||||
self.sid, transport)
|
||||
return await getattr(self, '_upgrade_' + transport)(environ)
|
||||
try:
|
||||
packets = await self.poll()
|
||||
except exceptions.QueueEmpty:
|
||||
await self.close(wait=False)
|
||||
raise
|
||||
return packets
|
||||
|
||||
async def handle_post_request(self, environ):
|
||||
"""Handle a long-polling POST request from the client."""
|
||||
length = int(environ.get('CONTENT_LENGTH', '0'))
|
||||
if length > self.server.max_http_buffer_size:
|
||||
raise exceptions.ContentTooLongError()
|
||||
else:
|
||||
body = await environ['wsgi.input'].read(length)
|
||||
p = payload.Payload(encoded_payload=body)
|
||||
for pkt in p.packets:
|
||||
await self.receive(pkt)
|
||||
|
||||
async def close(self, wait=True, abort=False):
|
||||
"""Close the socket connection."""
|
||||
if not self.closed and not self.closing:
|
||||
self.closing = True
|
||||
await self.server._trigger_event('disconnect', self.sid)
|
||||
if not abort:
|
||||
await self.send(packet.Packet(packet.CLOSE))
|
||||
self.closed = True
|
||||
if wait:
|
||||
await self.queue.join()
|
||||
|
||||
async def _upgrade_websocket(self, environ):
|
||||
"""Upgrade the connection from polling to websocket."""
|
||||
if self.upgraded:
|
||||
raise IOError('Socket has been upgraded already')
|
||||
if self.server._async['websocket'] is None or \
|
||||
self.server._async['websocket_class'] is None:
|
||||
# the selected async mode does not support websocket
|
||||
return self.server._bad_request()
|
||||
websocket_class = getattr(self.server._async['websocket'],
|
||||
self.server._async['websocket_class'])
|
||||
ws = websocket_class(self._websocket_handler)
|
||||
return await ws(environ)
|
||||
|
||||
async def _websocket_handler(self, ws):
|
||||
"""Engine.IO handler for websocket transport."""
|
||||
if self.connected:
|
||||
# the socket was already connected, so this is an upgrade
|
||||
await self.queue.join() # flush the queue first
|
||||
|
||||
pkt = await ws.wait()
|
||||
if pkt != packet.Packet(packet.PING,
|
||||
data=six.text_type('probe')).encode(
|
||||
always_bytes=False):
|
||||
self.server.logger.info(
|
||||
'%s: Failed websocket upgrade, no PING packet', self.sid)
|
||||
return
|
||||
await ws.send(packet.Packet(
|
||||
packet.PONG,
|
||||
data=six.text_type('probe')).encode(always_bytes=False))
|
||||
await self.send(packet.Packet(packet.NOOP))
|
||||
|
||||
pkt = await ws.wait()
|
||||
decoded_pkt = packet.Packet(encoded_packet=pkt)
|
||||
if decoded_pkt.packet_type != packet.UPGRADE:
|
||||
self.upgraded = False
|
||||
self.server.logger.info(
|
||||
('%s: Failed websocket upgrade, expected UPGRADE packet, '
|
||||
'received %s instead.'),
|
||||
self.sid, pkt)
|
||||
return
|
||||
self.upgraded = True
|
||||
else:
|
||||
self.connected = True
|
||||
self.upgraded = True
|
||||
|
||||
# start separate writer thread
|
||||
async def writer():
|
||||
while True:
|
||||
packets = None
|
||||
try:
|
||||
packets = await self.poll()
|
||||
except exceptions.QueueEmpty:
|
||||
break
|
||||
if not packets:
|
||||
# empty packet list returned -> connection closed
|
||||
break
|
||||
try:
|
||||
for pkt in packets:
|
||||
await ws.send(pkt.encode(always_bytes=False))
|
||||
except:
|
||||
break
|
||||
writer_task = asyncio.ensure_future(writer())
|
||||
|
||||
self.server.logger.info(
|
||||
'%s: Upgrade to websocket successful', self.sid)
|
||||
|
||||
while True:
|
||||
p = None
|
||||
wait_task = asyncio.ensure_future(ws.wait())
|
||||
try:
|
||||
p = await asyncio.wait_for(wait_task, 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
|
||||
# to appear when wait_task raises an exception before it gets
|
||||
# cancelled. Calling wait_task.exception() prevents the error
|
||||
# from being issued in Python 3.6, but causes other errors in
|
||||
# other versions, so we run it with all errors suppressed and
|
||||
# hope for the best.
|
||||
try:
|
||||
wait_task.exception()
|
||||
except:
|
||||
pass
|
||||
break
|
||||
except:
|
||||
break
|
||||
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)
|
||||
except exceptions.UnknownPacketError:
|
||||
pass
|
||||
except: # pragma: no cover
|
||||
# if we get an unexpected exception we log the error and exit
|
||||
# the connection properly
|
||||
self.server.logger.exception('Receive error')
|
||||
|
||||
await self.queue.put(None) # unlock the writer task so it can exit
|
||||
await asyncio.wait_for(writer_task, timeout=None)
|
||||
await self.close(wait=True, abort=True)
|
||||
14
venv/lib/python2.7/site-packages/engineio/exceptions.py
Normal file
14
venv/lib/python2.7/site-packages/engineio/exceptions.py
Normal file
@@ -0,0 +1,14 @@
|
||||
class EngineIOError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ContentTooLongError(EngineIOError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownPacketError(EngineIOError):
|
||||
pass
|
||||
|
||||
|
||||
class QueueEmpty(EngineIOError):
|
||||
pass
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/exceptions.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/exceptions.pyc
Normal file
Binary file not shown.
52
venv/lib/python2.7/site-packages/engineio/middleware.py
Normal file
52
venv/lib/python2.7/site-packages/engineio/middleware.py
Normal file
@@ -0,0 +1,52 @@
|
||||
class Middleware(object):
|
||||
"""WSGI middleware for Engine.IO.
|
||||
|
||||
This middleware dispatches traffic to an Engine.IO application, and
|
||||
optionally forwards regular HTTP traffic to a WSGI application.
|
||||
|
||||
:param engineio_app: The Engine.IO server.
|
||||
:param wsgi_app: The WSGI app that receives all other traffic.
|
||||
:param engineio_path: The endpoint where the Engine.IO application should
|
||||
be installed. The default value is appropriate for
|
||||
most cases.
|
||||
|
||||
Example usage::
|
||||
|
||||
import engineio
|
||||
import eventlet
|
||||
from . import wsgi_app
|
||||
|
||||
eio = engineio.Server()
|
||||
app = engineio.Middleware(eio, wsgi_app)
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
|
||||
"""
|
||||
def __init__(self, engineio_app, wsgi_app=None, engineio_path='engine.io'):
|
||||
self.engineio_app = engineio_app
|
||||
self.wsgi_app = wsgi_app
|
||||
self.engineio_path = engineio_path.strip('/')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
if 'gunicorn.socket' in environ:
|
||||
# gunicorn saves the socket under environ['gunicorn.socket'], while
|
||||
# eventlet saves it under environ['eventlet.input']. Eventlet also
|
||||
# stores the socket inside a wrapper class, while gunicon writes it
|
||||
# directly into the environment. To give eventlet's WebSocket
|
||||
# module access to this socket when running under gunicorn, here we
|
||||
# copy the socket to the eventlet format.
|
||||
class Input(object):
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
|
||||
def get_socket(self):
|
||||
return self.socket
|
||||
|
||||
environ['eventlet.input'] = Input(environ['gunicorn.socket'])
|
||||
path = environ['PATH_INFO']
|
||||
if path is not None and \
|
||||
path.startswith('/{0}/'.format(self.engineio_path)):
|
||||
return self.engineio_app.handle_request(environ, start_response)
|
||||
elif self.wsgi_app is not None:
|
||||
return self.wsgi_app(environ, start_response)
|
||||
else:
|
||||
start_response("404 Not Found", [('Content-type', 'text/plain')])
|
||||
return ['Not Found']
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/middleware.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/middleware.pyc
Normal file
Binary file not shown.
87
venv/lib/python2.7/site-packages/engineio/packet.py
Normal file
87
venv/lib/python2.7/site-packages/engineio/packet.py
Normal file
@@ -0,0 +1,87 @@
|
||||
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)
|
||||
|
||||
|
||||
class Packet(object):
|
||||
"""Engine.IO packet."""
|
||||
|
||||
json = _json
|
||||
|
||||
def __init__(self, packet_type=NOOP, data=None, binary=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):
|
||||
self.binary = False
|
||||
elif isinstance(data, binary_types):
|
||||
self.binary = True
|
||||
else:
|
||||
self.binary = False
|
||||
if encoded_packet:
|
||||
self.decode(encoded_packet)
|
||||
|
||||
def encode(self, b64=False, always_bytes=True):
|
||||
"""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')
|
||||
else:
|
||||
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')
|
||||
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 = 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
|
||||
else:
|
||||
self.binary = True
|
||||
self.data = None
|
||||
if len(encoded_packet) > 1:
|
||||
if self.binary:
|
||||
if b64:
|
||||
self.data = base64.b64decode(encoded_packet[1:])
|
||||
else:
|
||||
self.data = encoded_packet[1:]
|
||||
else:
|
||||
try:
|
||||
self.data = self.json.loads(
|
||||
encoded_packet[1:].decode('utf-8'))
|
||||
except ValueError:
|
||||
self.data = encoded_packet[1:].decode('utf-8')
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/packet.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/packet.pyc
Normal file
Binary file not shown.
84
venv/lib/python2.7/site-packages/engineio/payload.py
Normal file
84
venv/lib/python2.7/site-packages/engineio/payload.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import six
|
||||
|
||||
from . import packet
|
||||
|
||||
|
||||
class Payload(object):
|
||||
"""Engine.IO payload."""
|
||||
def __init__(self, packets=None, encoded_payload=None):
|
||||
self.packets = packets or []
|
||||
if encoded_payload is not None:
|
||||
self.decode(encoded_payload)
|
||||
|
||||
def encode(self, b64=False):
|
||||
"""Encode the payload for transmission."""
|
||||
encoded_payload = b''
|
||||
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
|
||||
return encoded_payload
|
||||
|
||||
def decode(self, encoded_payload):
|
||||
"""Decode a transmitted payload."""
|
||||
fixed_double_encode = False
|
||||
self.packets = []
|
||||
while encoded_payload:
|
||||
if six.byte2int(encoded_payload[0:1]) <= 1:
|
||||
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]))
|
||||
else:
|
||||
i = encoded_payload.find(b':')
|
||||
if i == -1:
|
||||
raise ValueError('invalid payload')
|
||||
# the packet_len below is given in utf-8 characters, but we
|
||||
# receive the payload as bytes, so down below this length is
|
||||
# adjusted to reflect byte length
|
||||
packet_len = int(encoded_payload[0:i])
|
||||
if not fixed_double_encode:
|
||||
# the engine.io javascript client sends text payloads with
|
||||
# a double UTF-8 encoding. Here we try to fix that mess and
|
||||
# restore the original packet
|
||||
try:
|
||||
# first we remove one UTF-8 encoding layer
|
||||
fixed_payload = encoded_payload.decode(
|
||||
'utf-8').encode('raw_unicode_escape')
|
||||
|
||||
# then we make sure the result can be decoded a second
|
||||
# time (this will raise an exception if not)
|
||||
fixed_payload.decode('utf-8')
|
||||
|
||||
# if a second utf-8 decode worked, then this appears to
|
||||
# be a double encoded packet, so here we keep the
|
||||
# packet after a single decode, since the packet class
|
||||
# will perform a decode as well, and in this case it is
|
||||
# not necessary to adjust the packet length
|
||||
encoded_payload = fixed_payload
|
||||
except:
|
||||
# if we couldn't apply a double utf-8 decode then
|
||||
# the packet must have been correct, so we just adjust
|
||||
# the packet length to be in bytes and not utf-8
|
||||
# characters and keep going
|
||||
packet_len += len(encoded_payload) - len(fixed_payload)
|
||||
fixed_double_encode = True
|
||||
pkt = encoded_payload[i + 1: i + 1 + packet_len]
|
||||
self.packets.append(packet.Packet(encoded_packet=pkt))
|
||||
encoded_payload = encoded_payload[i + 1 + packet_len:]
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/payload.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/payload.pyc
Normal file
Binary file not shown.
475
venv/lib/python2.7/site-packages/engineio/server.py
Normal file
475
venv/lib/python2.7/site-packages/engineio/server.py
Normal file
@@ -0,0 +1,475 @@
|
||||
import gzip
|
||||
import importlib
|
||||
import logging
|
||||
import uuid
|
||||
import zlib
|
||||
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from .exceptions import EngineIOError
|
||||
from . import packet
|
||||
from . import payload
|
||||
from . import socket
|
||||
|
||||
default_logger = logging.getLogger('engineio')
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""An Engine.IO server.
|
||||
|
||||
This class implements a fully compliant Engine.IO web server with support
|
||||
for websocket and long-polling transports.
|
||||
|
||||
:param async_mode: The asynchronous model to use. See the Deployment
|
||||
section in the documentation for a description of the
|
||||
available options. Valid async modes are "threading",
|
||||
"eventlet", "gevent" and "gevent_uwsgi". If this
|
||||
argument is not given, "eventlet" is tried first, then
|
||||
"gevent_uwsgi", then "gevent", and finally "threading".
|
||||
The first async mode that has all its dependencies
|
||||
installed is then 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.
|
||||
:param max_http_buffer_size: The maximum size of a message when using the
|
||||
polling transport.
|
||||
: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 cors_allowed_origins: List of origins that are allowed to connect
|
||||
to this server. All origins are allowed by
|
||||
default.
|
||||
: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``.
|
||||
: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 async_handlers: If set to ``True``, run message event handlers in
|
||||
non-blocking threads. To run handlers synchronously,
|
||||
set to ``False``. The default is ``True``.
|
||||
:param kwargs: Reserved for future extensions, any additional parameters
|
||||
given as keyword arguments will be silently ignored.
|
||||
"""
|
||||
compression_methods = ['gzip', 'deflate']
|
||||
event_names = ['connect', 'disconnect', 'message']
|
||||
|
||||
def __init__(self, async_mode=None, ping_timeout=60, ping_interval=25,
|
||||
max_http_buffer_size=100000000, allow_upgrades=True,
|
||||
http_compression=True, compression_threshold=1024,
|
||||
cookie='io', cors_allowed_origins=None,
|
||||
cors_credentials=True, logger=False, json=None,
|
||||
async_handlers=True, **kwargs):
|
||||
self.ping_timeout = ping_timeout
|
||||
self.ping_interval = ping_interval
|
||||
self.max_http_buffer_size = max_http_buffer_size
|
||||
self.allow_upgrades = allow_upgrades
|
||||
self.http_compression = http_compression
|
||||
self.compression_threshold = compression_threshold
|
||||
self.cookie = cookie
|
||||
self.cors_allowed_origins = cors_allowed_origins
|
||||
self.cors_credentials = cors_credentials
|
||||
self.async_handlers = async_handlers
|
||||
self.sockets = {}
|
||||
self.handlers = {}
|
||||
if json is not None:
|
||||
packet.Packet.json = json
|
||||
if not isinstance(logger, bool):
|
||||
self.logger = logger
|
||||
else:
|
||||
self.logger = default_logger
|
||||
if not logging.root.handlers and \
|
||||
self.logger.level == logging.NOTSET:
|
||||
if logger:
|
||||
self.logger.setLevel(logging.INFO)
|
||||
else:
|
||||
self.logger.setLevel(logging.ERROR)
|
||||
self.logger.addHandler(logging.StreamHandler())
|
||||
if async_mode is None:
|
||||
modes = self.async_modes()
|
||||
else:
|
||||
modes = [async_mode]
|
||||
self._async = None
|
||||
self.async_mode = None
|
||||
for mode in modes:
|
||||
try:
|
||||
self._async = importlib.import_module(
|
||||
'engineio.async_' + mode)._async
|
||||
asyncio_based = self._async['asyncio'] \
|
||||
if 'asyncio' in self._async else False
|
||||
if asyncio_based != self.is_asyncio_based():
|
||||
continue
|
||||
self.async_mode = mode
|
||||
break
|
||||
except ImportError:
|
||||
pass
|
||||
if self.async_mode is None:
|
||||
raise ValueError('Invalid async_mode specified')
|
||||
if self.is_asyncio_based() and \
|
||||
('asyncio' not in self._async or
|
||||
not self._async['asyncio']): # pragma: no cover
|
||||
raise ValueError('The selected async_mode is not asyncio '
|
||||
'compatible')
|
||||
if not self.is_asyncio_based() and 'asyncio' in self._async and \
|
||||
self._async['asyncio']: # pragma: no cover
|
||||
raise ValueError('The selected async_mode requires asyncio and '
|
||||
'must use the AsyncServer class')
|
||||
self.logger.info('Server initialized for %s.', self.async_mode)
|
||||
|
||||
def is_asyncio_based(self):
|
||||
return False
|
||||
|
||||
def async_modes(self):
|
||||
return ['eventlet', 'gevent_uwsgi', 'gevent', 'threading']
|
||||
|
||||
def on(self, event, handler=None):
|
||||
"""Register an event handler.
|
||||
|
||||
:param event: The event name. Can be ``'connect'``, ``'message'`` or
|
||||
``'disconnect'``.
|
||||
:param handler: The function that should be invoked to handle the
|
||||
event. When this parameter is not given, the method
|
||||
acts as a decorator for the handler function.
|
||||
|
||||
Example usage::
|
||||
|
||||
# as a decorator:
|
||||
@eio.on('connect')
|
||||
def connect_handler(sid, environ):
|
||||
print('Connection request')
|
||||
if environ['REMOTE_ADDR'] in blacklisted:
|
||||
return False # reject
|
||||
|
||||
# as a method:
|
||||
def message_handler(sid, msg):
|
||||
print('Received message: ', msg)
|
||||
eio.send(sid, 'response')
|
||||
eio.on('message', message_handler)
|
||||
|
||||
The handler function receives the ``sid`` (session ID) for the
|
||||
client as first argument. The ``'connect'`` event handler receives the
|
||||
WSGI environment as a second argument, and can return ``False`` to
|
||||
reject the connection. The ``'message'`` handler receives the message
|
||||
payload as a second argument. The ``'disconnect'`` handler does not
|
||||
take a second argument.
|
||||
"""
|
||||
if event not in self.event_names:
|
||||
raise ValueError('Invalid event')
|
||||
|
||||
def set_handler(handler):
|
||||
self.handlers[event] = handler
|
||||
return handler
|
||||
|
||||
if handler is None:
|
||||
return set_handler
|
||||
set_handler(handler)
|
||||
|
||||
def send(self, sid, data, binary=None):
|
||||
"""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)
|
||||
except KeyError:
|
||||
# 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))
|
||||
|
||||
def disconnect(self, sid=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
:param sid: The session id of the client to close. If this parameter
|
||||
is not given, then all clients are closed.
|
||||
"""
|
||||
if sid is not None:
|
||||
try:
|
||||
socket = self._get_socket(sid)
|
||||
except KeyError: # pragma: no cover
|
||||
# the socket was already closed or gone
|
||||
pass
|
||||
else:
|
||||
socket.close()
|
||||
del self.sockets[sid]
|
||||
else:
|
||||
for client in six.itervalues(self.sockets):
|
||||
client.close()
|
||||
self.sockets = {}
|
||||
|
||||
def transport(self, sid):
|
||||
"""Return the name of the transport used by the client.
|
||||
|
||||
The two possible values returned by this function are ``'polling'``
|
||||
and ``'websocket'``.
|
||||
|
||||
:param sid: The session of the client.
|
||||
"""
|
||||
return 'websocket' if self._get_socket(sid).upgraded else 'polling'
|
||||
|
||||
def handle_request(self, environ, start_response):
|
||||
"""Handle an HTTP request from the client.
|
||||
|
||||
This is the entry point of the Engine.IO application, using the same
|
||||
interface as a WSGI application. For the typical usage, this function
|
||||
is invoked by the :class:`Middleware` instance, but it can be invoked
|
||||
directly when the middleware is not used.
|
||||
|
||||
:param environ: The WSGI environment.
|
||||
:param start_response: The WSGI ``start_response`` function.
|
||||
|
||||
This function returns the HTTP response body to deliver to the client
|
||||
as a byte sequence.
|
||||
"""
|
||||
method = environ['REQUEST_METHOD']
|
||||
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', ''))
|
||||
if 'j' in query:
|
||||
self.logger.warning('JSONP requests are not supported')
|
||||
r = self._bad_request()
|
||||
else:
|
||||
sid = query['sid'][0] if 'sid' in query else None
|
||||
b64 = False
|
||||
if 'b64' in query:
|
||||
if query['b64'][0] == "1" or query['b64'][0].lower() == "true":
|
||||
b64 = True
|
||||
if 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:
|
||||
r = self._handle_connect(environ, start_response,
|
||||
transport, b64)
|
||||
else:
|
||||
if sid not in self.sockets:
|
||||
self.logger.warning('Invalid session %s', sid)
|
||||
r = self._bad_request()
|
||||
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)
|
||||
else:
|
||||
r = packets
|
||||
except EngineIOError:
|
||||
if sid in self.sockets: # pragma: no cover
|
||||
self.disconnect(sid)
|
||||
r = self._bad_request()
|
||||
if sid in self.sockets and self.sockets[sid].closed:
|
||||
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()
|
||||
else:
|
||||
socket = self._get_socket(sid)
|
||||
try:
|
||||
socket.handle_post_request(environ)
|
||||
r = self._ok()
|
||||
except EngineIOError:
|
||||
if sid in self.sockets: # pragma: no cover
|
||||
self.disconnect(sid)
|
||||
r = self._bad_request()
|
||||
except: # pragma: no cover
|
||||
# for any other unexpected errors, we log the error
|
||||
# and keep going
|
||||
self.logger.exception('post request handler error')
|
||||
r = self._ok()
|
||||
else:
|
||||
self.logger.warning('Method %s not supported', method)
|
||||
r = self._method_not_found()
|
||||
if not isinstance(r, dict):
|
||||
return r or []
|
||||
if self.http_compression and \
|
||||
len(r['response']) >= self.compression_threshold:
|
||||
encodings = [e.split(';')[0].strip() for e in
|
||||
environ.get('HTTP_ACCEPT_ENCODING', '').split(',')]
|
||||
for encoding in encodings:
|
||||
if encoding in self.compression_methods:
|
||||
r['response'] = \
|
||||
getattr(self, '_' + encoding)(r['response'])
|
||||
r['headers'] += [('Content-Encoding', encoding)]
|
||||
break
|
||||
cors_headers = self._cors_headers(environ)
|
||||
start_response(r['status'], r['headers'] + cors_headers)
|
||||
return [r['response']]
|
||||
|
||||
def start_background_task(self, target, *args, **kwargs):
|
||||
"""Start a background task using the appropriate async model.
|
||||
|
||||
This is a utility function that applications can use to start a
|
||||
background task using the method that is compatible with the
|
||||
selected async mode.
|
||||
|
||||
:param target: the target function to execute.
|
||||
:param args: arguments to pass to the function.
|
||||
:param kwargs: keyword arguments to pass to the function.
|
||||
|
||||
This function returns an object compatible with the `Thread` class in
|
||||
the Python standard library. The `start()` method on this object is
|
||||
already called by this function.
|
||||
"""
|
||||
th = getattr(self._async['threading'],
|
||||
self._async['thread_class'])(target=target, args=args,
|
||||
kwargs=kwargs)
|
||||
th.start()
|
||||
return th # pragma: no cover
|
||||
|
||||
def sleep(self, seconds=0):
|
||||
"""Sleep for the requested amount of time using the appropriate async
|
||||
model.
|
||||
|
||||
This is a utility function that applications can use to put a task to
|
||||
sleep without having to worry about using the correct call for the
|
||||
selected async mode.
|
||||
"""
|
||||
return self._async['sleep'](seconds)
|
||||
|
||||
def _generate_id(self):
|
||||
"""Generate a unique session id."""
|
||||
return uuid.uuid4().hex
|
||||
|
||||
def _handle_connect(self, environ, start_response, transport, b64=False):
|
||||
"""Handle a client connection request."""
|
||||
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)})
|
||||
s.send(pkt)
|
||||
|
||||
ret = self._trigger_event('connect', sid, environ, run_async=False)
|
||||
if ret is False:
|
||||
del self.sockets[sid]
|
||||
self.logger.warning('Application rejected connection')
|
||||
return self._unauthorized()
|
||||
|
||||
if transport == 'websocket':
|
||||
ret = s.handle_get_request(environ, start_response)
|
||||
if s.closed:
|
||||
# websocket connection ended, so we are done
|
||||
del self.sockets[sid]
|
||||
return ret
|
||||
else:
|
||||
s.connected = True
|
||||
headers = None
|
||||
if self.cookie:
|
||||
headers = [('Set-Cookie', self.cookie + '=' + sid)]
|
||||
return self._ok(s.poll(), headers=headers, b64=b64)
|
||||
|
||||
def _upgrades(self, sid, transport):
|
||||
"""Return the list of possible upgrades for a client connection."""
|
||||
if not self.allow_upgrades or self._get_socket(sid).upgraded or \
|
||||
self._async['websocket_class'] is None or \
|
||||
transport == 'websocket':
|
||||
return []
|
||||
return ['websocket']
|
||||
|
||||
def _trigger_event(self, event, *args, **kwargs):
|
||||
"""Invoke an event handler."""
|
||||
run_async = kwargs.pop('run_async', False)
|
||||
if event in self.handlers:
|
||||
if run_async:
|
||||
return self.start_background_task(self.handlers[event], *args)
|
||||
else:
|
||||
try:
|
||||
return self.handlers[event](*args)
|
||||
except:
|
||||
self.logger.exception(event + ' handler error')
|
||||
if event == 'connect':
|
||||
# if connect handler raised error we reject the
|
||||
# connection
|
||||
return False
|
||||
|
||||
def _get_socket(self, sid):
|
||||
"""Return the socket object for a given session."""
|
||||
try:
|
||||
s = self.sockets[sid]
|
||||
except KeyError:
|
||||
raise KeyError('Session not found')
|
||||
if s.closed:
|
||||
del self.sockets[sid]
|
||||
raise KeyError('Session is disconnected')
|
||||
return s
|
||||
|
||||
def _ok(self, packets=None, headers=None, b64=False):
|
||||
"""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')]
|
||||
return {'status': '200 OK',
|
||||
'headers': headers,
|
||||
'response': payload.Payload(packets=packets).encode(b64)}
|
||||
else:
|
||||
return {'status': '200 OK',
|
||||
'headers': [('Content-Type', 'text/plain')],
|
||||
'response': b'OK'}
|
||||
|
||||
def _bad_request(self):
|
||||
"""Generate a bad request HTTP error response."""
|
||||
return {'status': '400 BAD REQUEST',
|
||||
'headers': [('Content-Type', 'text/plain')],
|
||||
'response': b'Bad Request'}
|
||||
|
||||
def _method_not_found(self):
|
||||
"""Generate a method not found HTTP error response."""
|
||||
return {'status': '405 METHOD NOT FOUND',
|
||||
'headers': [('Content-Type', 'text/plain')],
|
||||
'response': b'Method Not Found'}
|
||||
|
||||
def _unauthorized(self):
|
||||
"""Generate a unauthorized HTTP error response."""
|
||||
return {'status': '401 UNAUTHORIZED',
|
||||
'headers': [('Content-Type', 'text/plain')],
|
||||
'response': b'Unauthorized'}
|
||||
|
||||
def _cors_headers(self, environ):
|
||||
"""Return the cross-origin-resource-sharing headers."""
|
||||
if self.cors_allowed_origins is not None and \
|
||||
environ.get('HTTP_ORIGIN', '') not in \
|
||||
self.cors_allowed_origins:
|
||||
return []
|
||||
if 'HTTP_ORIGIN' in environ:
|
||||
headers = [('Access-Control-Allow-Origin', environ['HTTP_ORIGIN'])]
|
||||
else:
|
||||
headers = [('Access-Control-Allow-Origin', '*')]
|
||||
if self.cors_credentials:
|
||||
headers += [('Access-Control-Allow-Credentials', 'true')]
|
||||
return headers
|
||||
|
||||
def _gzip(self, response):
|
||||
"""Apply gzip compression to a response."""
|
||||
bytesio = six.BytesIO()
|
||||
with gzip.GzipFile(fileobj=bytesio, mode='w') as gz:
|
||||
gz.write(response)
|
||||
return bytesio.getvalue()
|
||||
|
||||
def _deflate(self, response):
|
||||
"""Apply deflate compression to a response."""
|
||||
return zlib.compress(response)
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/server.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/server.pyc
Normal file
Binary file not shown.
222
venv/lib/python2.7/site-packages/engineio/socket.py
Normal file
222
venv/lib/python2.7/site-packages/engineio/socket.py
Normal file
@@ -0,0 +1,222 @@
|
||||
import six
|
||||
import time
|
||||
|
||||
from . import exceptions
|
||||
from . import packet
|
||||
from . import payload
|
||||
|
||||
|
||||
class Socket(object):
|
||||
"""An Engine.IO socket."""
|
||||
upgrade_protocols = ['websocket']
|
||||
|
||||
def __init__(self, server, sid):
|
||||
self.server = server
|
||||
self.sid = sid
|
||||
self.queue = self.create_queue()
|
||||
self.last_ping = time.time()
|
||||
self.connected = False
|
||||
self.upgraded = False
|
||||
self.closing = False
|
||||
self.closed = False
|
||||
|
||||
def create_queue(self):
|
||||
return getattr(self.server._async['queue'],
|
||||
self.server._async['queue_class'])()
|
||||
|
||||
def poll(self):
|
||||
"""Wait for packets to send to the client."""
|
||||
try:
|
||||
packets = [self.queue.get(timeout=self.server.ping_timeout)]
|
||||
self.queue.task_done()
|
||||
except self.server._async['queue'].Empty:
|
||||
raise exceptions.QueueEmpty()
|
||||
if packets == [None]:
|
||||
return []
|
||||
try:
|
||||
packets.append(self.queue.get(block=False))
|
||||
self.queue.task_done()
|
||||
except self.server._async['queue'].Empty:
|
||||
pass
|
||||
return packets
|
||||
|
||||
def receive(self, pkt):
|
||||
"""Receive packet from the client."""
|
||||
packet_name = packet.packet_names[pkt.packet_type] \
|
||||
if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN'
|
||||
self.server.logger.info('%s: Received packet %s data %s',
|
||||
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))
|
||||
elif pkt.packet_type == packet.MESSAGE:
|
||||
self.server._trigger_event('message', self.sid, pkt.data,
|
||||
run_async=self.server.async_handlers)
|
||||
elif pkt.packet_type == packet.UPGRADE:
|
||||
self.send(packet.Packet(packet.NOOP))
|
||||
elif pkt.packet_type == packet.CLOSE:
|
||||
self.close(wait=False, abort=True)
|
||||
else:
|
||||
raise exceptions.UnknownPacketError()
|
||||
|
||||
def send(self, pkt):
|
||||
"""Send a packet to the client."""
|
||||
if self.closed:
|
||||
raise IOError('Socket is closed')
|
||||
if time.time() - self.last_ping > self.server.ping_timeout:
|
||||
self.server.logger.info('%s: Client is gone, closing socket',
|
||||
self.sid)
|
||||
self.close(wait=False, abort=True)
|
||||
return
|
||||
self.queue.put(pkt)
|
||||
self.server.logger.info('%s: Sending packet %s data %s',
|
||||
self.sid, packet.packet_names[pkt.packet_type],
|
||||
pkt.data if not isinstance(pkt.data, bytes)
|
||||
else '<binary>')
|
||||
|
||||
def handle_get_request(self, environ, start_response):
|
||||
"""Handle a long-polling GET request from the client."""
|
||||
connections = [
|
||||
s.strip()
|
||||
for s in environ.get('HTTP_CONNECTION', '').lower().split(',')]
|
||||
transport = environ.get('HTTP_UPGRADE', '').lower()
|
||||
if 'upgrade' in connections and transport in self.upgrade_protocols:
|
||||
self.server.logger.info('%s: Received request to upgrade to %s',
|
||||
self.sid, transport)
|
||||
return getattr(self, '_upgrade_' + transport)(environ,
|
||||
start_response)
|
||||
try:
|
||||
packets = self.poll()
|
||||
except exceptions.QueueEmpty:
|
||||
self.close(wait=False)
|
||||
raise
|
||||
return packets
|
||||
|
||||
def handle_post_request(self, environ):
|
||||
"""Handle a long-polling POST request from the client."""
|
||||
length = int(environ.get('CONTENT_LENGTH', '0'))
|
||||
if length > self.server.max_http_buffer_size:
|
||||
raise exceptions.ContentTooLongError()
|
||||
else:
|
||||
body = environ['wsgi.input'].read(length)
|
||||
p = payload.Payload(encoded_payload=body)
|
||||
for pkt in p.packets:
|
||||
self.receive(pkt)
|
||||
|
||||
def close(self, wait=True, abort=False):
|
||||
"""Close the socket connection."""
|
||||
if not self.closed and not self.closing:
|
||||
self.closing = True
|
||||
self.server._trigger_event('disconnect', self.sid, run_async=False)
|
||||
if not abort:
|
||||
self.send(packet.Packet(packet.CLOSE))
|
||||
self.closed = True
|
||||
if wait:
|
||||
self.queue.join()
|
||||
|
||||
def _upgrade_websocket(self, environ, start_response):
|
||||
"""Upgrade the connection from polling to websocket."""
|
||||
if self.upgraded:
|
||||
raise IOError('Socket has been upgraded already')
|
||||
if self.server._async['websocket'] is None or \
|
||||
self.server._async['websocket_class'] is None:
|
||||
# the selected async mode does not support websocket
|
||||
return self.server._bad_request()
|
||||
websocket_class = getattr(self.server._async['websocket'],
|
||||
self.server._async['websocket_class'])
|
||||
ws = websocket_class(self._websocket_handler)
|
||||
return ws(environ, start_response)
|
||||
|
||||
def _websocket_handler(self, ws):
|
||||
"""Engine.IO handler for websocket transport."""
|
||||
# try to set a socket timeout matching the configured ping interval
|
||||
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)
|
||||
|
||||
if self.connected:
|
||||
# the socket was already connected, so this is an upgrade
|
||||
self.queue.join() # flush the queue first
|
||||
|
||||
pkt = ws.wait()
|
||||
if pkt != packet.Packet(packet.PING,
|
||||
data=six.text_type('probe')).encode(
|
||||
always_bytes=False):
|
||||
self.server.logger.info(
|
||||
'%s: Failed websocket upgrade, no PING packet', self.sid)
|
||||
return []
|
||||
ws.send(packet.Packet(
|
||||
packet.PONG,
|
||||
data=six.text_type('probe')).encode(always_bytes=False))
|
||||
self.send(packet.Packet(packet.NOOP))
|
||||
|
||||
pkt = ws.wait()
|
||||
decoded_pkt = packet.Packet(encoded_packet=pkt)
|
||||
if decoded_pkt.packet_type != packet.UPGRADE:
|
||||
self.upgraded = False
|
||||
self.server.logger.info(
|
||||
('%s: Failed websocket upgrade, expected UPGRADE packet, '
|
||||
'received %s instead.'),
|
||||
self.sid, pkt)
|
||||
return []
|
||||
self.upgraded = True
|
||||
else:
|
||||
self.connected = True
|
||||
self.upgraded = True
|
||||
|
||||
# start separate writer thread
|
||||
def writer():
|
||||
while True:
|
||||
packets = None
|
||||
try:
|
||||
packets = self.poll()
|
||||
except exceptions.QueueEmpty:
|
||||
break
|
||||
if not packets:
|
||||
# empty packet list returned -> connection closed
|
||||
break
|
||||
try:
|
||||
for pkt in packets:
|
||||
ws.send(pkt.encode(always_bytes=False))
|
||||
except:
|
||||
break
|
||||
writer_task = self.server.start_background_task(writer)
|
||||
|
||||
self.server.logger.info(
|
||||
'%s: Upgrade to websocket successful', self.sid)
|
||||
|
||||
while True:
|
||||
p = None
|
||||
try:
|
||||
p = ws.wait()
|
||||
except Exception as e:
|
||||
# if the socket is already closed, we can assume this is a
|
||||
# downstream error of that
|
||||
if not self.closed: # pragma: no cover
|
||||
self.server.logger.info(
|
||||
'%s: Unexpected error "%s", closing connection',
|
||||
self.sid, str(e))
|
||||
break
|
||||
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)
|
||||
except exceptions.UnknownPacketError:
|
||||
pass
|
||||
except: # pragma: no cover
|
||||
# if we get an unexpected exception we log the error and exit
|
||||
# the connection properly
|
||||
self.server.logger.exception('Receive error')
|
||||
break
|
||||
|
||||
self.queue.put(None) # unlock the writer task so that it can exit
|
||||
writer_task.join()
|
||||
self.close(wait=True, abort=True)
|
||||
|
||||
return []
|
||||
BIN
venv/lib/python2.7/site-packages/engineio/socket.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/engineio/socket.pyc
Normal file
Binary file not shown.
Reference in New Issue
Block a user