import asyncio import sys from urllib.parse import urlsplit from .. import exceptions import tornado.web import tornado.websocket 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, str): if engineio_server.cors_allowed_origins == '*': self.allowed_origins = None else: self.allowed_origins = [ engineio_server.cors_allowed_origins] else: self.allowed_origins = engineio_server.cors_allowed_origins self.receive_queue = asyncio.Queue() async def get(self, *args, **kwargs): if self.request.headers.get('Upgrade', '').lower() == 'websocket': ret = super().get(*args, **kwargs) if asyncio.iscoroutine(ret): await ret else: await engineio_server.handle_request(self) async def open(self, *args, **kwargs): # this is the handler for the websocket request asyncio.ensure_future(engineio_server.handle_request(self)) async def post(self, *args, **kwargs): await engineio_server.handle_request(self) async def options(self, *args, **kwargs): await engineio_server.handle_request(self) async def on_message(self, message): await self.receive_queue.put(message) async def get_next_message(self): return await self.receive_queue.get() def on_close(self): self.receive_queue.put_nowait(None) def check_origin(self, origin): if self.allowed_origins is None or origin in self.allowed_origins: return True return super().check_origin(origin) def get_compression_options(self): # enable compression return {} return Handler def translate_request(handler): """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 payload = handler.request.body uri_parts = urlsplit(handler.request.path) full_uri = handler.request.path if handler.request.query: # pragma: no cover full_uri += '?' + handler.request.query environ = { 'wsgi.input': AwaitablePayload(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': handler.request.method, 'QUERY_STRING': handler.request.query or '', 'RAW_URI': full_uri, 'SERVER_PROTOCOL': 'HTTP/%s' % handler.request.version, 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '0', 'SERVER_NAME': 'aiohttp', 'SERVER_PORT': '0', 'tornado.handler': handler } for hdr_name, hdr_value in handler.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('-', '_') 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, environ): """This function generates an appropriate response object for this async mode. """ tornado_handler = environ['tornado.handler'] try: tornado_handler.set_status(int(status.split()[0])) except RuntimeError: # pragma: no cover # for websocket connections Tornado does not accept a response, since # it already emitted the 101 status code return for header, value in headers: tornado_handler.set_header(header, value) tornado_handler.write(payload) tornado_handler.finish() class WebSocket(object): # pragma: no cover """ This wrapper class provides a tornado WebSocket interface that is somewhat compatible with eventlet's implementation. """ def __init__(self, handler, server): self.handler = handler self.tornado_handler = None async def __call__(self, environ): self.tornado_handler = environ['tornado.handler'] self.environ = environ await self.handler(self) async def close(self): self.tornado_handler.close() async def send(self, message): try: self.tornado_handler.write_message( message, binary=isinstance(message, bytes)) except tornado.websocket.WebSocketClosedError: raise exceptions.EngineIOError() async def wait(self): msg = await self.tornado_handler.get_next_message() if not isinstance(msg, bytes) and \ not isinstance(msg, str): raise IOError() return msg _async = { 'asyncio': True, 'translate_request': translate_request, 'make_response': make_response, 'websocket': WebSocket, }