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

224 строки
8.2KB

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