Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

215 строки
7.8KB

  1. import os
  2. import sys
  3. from engineio.static_files import get_static_file
  4. class ASGIApp:
  5. """ASGI application middleware for Engine.IO.
  6. This middleware dispatches traffic to an Engine.IO application. It can
  7. also serve a list of static files to the client, or forward unrelated
  8. HTTP traffic to another ASGI application.
  9. :param engineio_server: The Engine.IO server. Must be an instance of the
  10. ``engineio.AsyncServer`` class.
  11. :param static_files: A dictionary with static file mapping rules. See the
  12. documentation for details on this argument.
  13. :param other_asgi_app: A separate ASGI app that receives all other traffic.
  14. :param engineio_path: The endpoint where the Engine.IO application should
  15. be installed. The default value is appropriate for
  16. most cases.
  17. Example usage::
  18. import engineio
  19. import uvicorn
  20. eio = engineio.AsyncServer()
  21. app = engineio.ASGIApp(eio, static_files={
  22. '/': {'content_type': 'text/html', 'filename': 'index.html'},
  23. '/index.html': {'content_type': 'text/html',
  24. 'filename': 'index.html'},
  25. })
  26. uvicorn.run(app, '127.0.0.1', 5000)
  27. """
  28. def __init__(self, engineio_server, other_asgi_app=None,
  29. static_files=None, engineio_path='engine.io'):
  30. self.engineio_server = engineio_server
  31. self.other_asgi_app = other_asgi_app
  32. self.engineio_path = engineio_path.strip('/')
  33. self.static_files = static_files or {}
  34. async def __call__(self, scope, receive, send):
  35. if scope['type'] in ['http', 'websocket'] and \
  36. scope['path'].startswith('/{0}/'.format(self.engineio_path)):
  37. await self.engineio_server.handle_request(scope, receive, send)
  38. else:
  39. static_file = get_static_file(scope['path'], self.static_files) \
  40. if scope['type'] == 'http' and self.static_files else None
  41. if static_file:
  42. await self.serve_static_file(static_file, receive, send)
  43. elif self.other_asgi_app is not None:
  44. await self.other_asgi_app(scope, receive, send)
  45. elif scope['type'] == 'lifespan':
  46. await self.lifespan(receive, send)
  47. else:
  48. await self.not_found(receive, send)
  49. async def serve_static_file(self, static_file, receive,
  50. send): # pragma: no cover
  51. event = await receive()
  52. if event['type'] == 'http.request':
  53. if os.path.exists(static_file['filename']):
  54. with open(static_file['filename'], 'rb') as f:
  55. payload = f.read()
  56. await send({'type': 'http.response.start',
  57. 'status': 200,
  58. 'headers': [(b'Content-Type', static_file[
  59. 'content_type'].encode('utf-8'))]})
  60. await send({'type': 'http.response.body',
  61. 'body': payload})
  62. else:
  63. await self.not_found(receive, send)
  64. async def lifespan(self, receive, send):
  65. event = await receive()
  66. if event['type'] == 'lifespan.startup':
  67. await send({'type': 'lifespan.startup.complete'})
  68. elif event['type'] == 'lifespan.shutdown':
  69. await send({'type': 'lifespan.shutdown.complete'})
  70. async def not_found(self, receive, send):
  71. """Return a 404 Not Found error to the client."""
  72. await send({'type': 'http.response.start',
  73. 'status': 404,
  74. 'headers': [(b'Content-Type', b'text/plain')]})
  75. await send({'type': 'http.response.body',
  76. 'body': b'Not Found'})
  77. async def translate_request(scope, receive, send):
  78. class AwaitablePayload(object): # pragma: no cover
  79. def __init__(self, payload):
  80. self.payload = payload or b''
  81. async def read(self, length=None):
  82. if length is None:
  83. r = self.payload
  84. self.payload = b''
  85. else:
  86. r = self.payload[:length]
  87. self.payload = self.payload[length:]
  88. return r
  89. event = await receive()
  90. payload = b''
  91. if event['type'] == 'http.request':
  92. payload += event.get('body') or b''
  93. while event.get('more_body'):
  94. event = await receive()
  95. if event['type'] == 'http.request':
  96. payload += event.get('body') or b''
  97. elif event['type'] == 'websocket.connect':
  98. await send({'type': 'websocket.accept'})
  99. else:
  100. return {}
  101. raw_uri = scope['path'].encode('utf-8')
  102. if 'query_string' in scope and scope['query_string']:
  103. raw_uri += b'?' + scope['query_string']
  104. environ = {
  105. 'wsgi.input': AwaitablePayload(payload),
  106. 'wsgi.errors': sys.stderr,
  107. 'wsgi.version': (1, 0),
  108. 'wsgi.async': True,
  109. 'wsgi.multithread': False,
  110. 'wsgi.multiprocess': False,
  111. 'wsgi.run_once': False,
  112. 'SERVER_SOFTWARE': 'asgi',
  113. 'REQUEST_METHOD': scope.get('method', 'GET'),
  114. 'PATH_INFO': scope['path'],
  115. 'QUERY_STRING': scope.get('query_string', b'').decode('utf-8'),
  116. 'RAW_URI': raw_uri.decode('utf-8'),
  117. 'SCRIPT_NAME': '',
  118. 'SERVER_PROTOCOL': 'HTTP/1.1',
  119. 'REMOTE_ADDR': '127.0.0.1',
  120. 'REMOTE_PORT': '0',
  121. 'SERVER_NAME': 'asgi',
  122. 'SERVER_PORT': '0',
  123. 'asgi.receive': receive,
  124. 'asgi.send': send,
  125. }
  126. for hdr_name, hdr_value in scope['headers']:
  127. hdr_name = hdr_name.upper().decode('utf-8')
  128. hdr_value = hdr_value.decode('utf-8')
  129. if hdr_name == 'CONTENT-TYPE':
  130. environ['CONTENT_TYPE'] = hdr_value
  131. continue
  132. elif hdr_name == 'CONTENT-LENGTH':
  133. environ['CONTENT_LENGTH'] = hdr_value
  134. continue
  135. key = 'HTTP_%s' % hdr_name.replace('-', '_')
  136. if key in environ:
  137. hdr_value = '%s,%s' % (environ[key], hdr_value)
  138. environ[key] = hdr_value
  139. environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http')
  140. return environ
  141. async def make_response(status, headers, payload, environ):
  142. headers = [(h[0].encode('utf-8'), h[1].encode('utf-8')) for h in headers]
  143. await environ['asgi.send']({'type': 'http.response.start',
  144. 'status': int(status.split(' ')[0]),
  145. 'headers': headers})
  146. await environ['asgi.send']({'type': 'http.response.body',
  147. 'body': payload})
  148. class WebSocket(object): # pragma: no cover
  149. """
  150. This wrapper class provides an asgi WebSocket interface that is
  151. somewhat compatible with eventlet's implementation.
  152. """
  153. def __init__(self, handler):
  154. self.handler = handler
  155. self.asgi_receive = None
  156. self.asgi_send = None
  157. async def __call__(self, environ):
  158. self.asgi_receive = environ['asgi.receive']
  159. self.asgi_send = environ['asgi.send']
  160. await self.handler(self)
  161. async def close(self):
  162. await self.asgi_send({'type': 'websocket.close'})
  163. async def send(self, message):
  164. msg_bytes = None
  165. msg_text = None
  166. if isinstance(message, bytes):
  167. msg_bytes = message
  168. else:
  169. msg_text = message
  170. await self.asgi_send({'type': 'websocket.send',
  171. 'bytes': msg_bytes,
  172. 'text': msg_text})
  173. async def wait(self):
  174. event = await self.asgi_receive()
  175. if event['type'] != 'websocket.receive':
  176. raise IOError()
  177. return event.get('bytes') or event.get('text')
  178. _async = {
  179. 'asyncio': True,
  180. 'translate_request': translate_request,
  181. 'make_response': make_response,
  182. 'websocket': WebSocket,
  183. }