Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

134 linhas
4.6 KiB

  1. import io
  2. import math
  3. import sys
  4. import typing
  5. import anyio
  6. from starlette.types import Receive, Scope, Send
  7. def build_environ(scope: Scope, body: bytes) -> dict:
  8. """
  9. Builds a scope and request body into a WSGI environ object.
  10. """
  11. environ = {
  12. "REQUEST_METHOD": scope["method"],
  13. "SCRIPT_NAME": scope.get("root_path", "").encode("utf8").decode("latin1"),
  14. "PATH_INFO": scope["path"].encode("utf8").decode("latin1"),
  15. "QUERY_STRING": scope["query_string"].decode("ascii"),
  16. "SERVER_PROTOCOL": f"HTTP/{scope['http_version']}",
  17. "wsgi.version": (1, 0),
  18. "wsgi.url_scheme": scope.get("scheme", "http"),
  19. "wsgi.input": io.BytesIO(body),
  20. "wsgi.errors": sys.stdout,
  21. "wsgi.multithread": True,
  22. "wsgi.multiprocess": True,
  23. "wsgi.run_once": False,
  24. }
  25. # Get server name and port - required in WSGI, not in ASGI
  26. server = scope.get("server") or ("localhost", 80)
  27. environ["SERVER_NAME"] = server[0]
  28. environ["SERVER_PORT"] = server[1]
  29. # Get client IP address
  30. if scope.get("client"):
  31. environ["REMOTE_ADDR"] = scope["client"][0]
  32. # Go through headers and make them into environ entries
  33. for name, value in scope.get("headers", []):
  34. name = name.decode("latin1")
  35. if name == "content-length":
  36. corrected_name = "CONTENT_LENGTH"
  37. elif name == "content-type":
  38. corrected_name = "CONTENT_TYPE"
  39. else:
  40. corrected_name = f"HTTP_{name}".upper().replace("-", "_")
  41. # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in
  42. # case
  43. value = value.decode("latin1")
  44. if corrected_name in environ:
  45. value = environ[corrected_name] + "," + value
  46. environ[corrected_name] = value
  47. return environ
  48. class WSGIMiddleware:
  49. def __init__(self, app: typing.Callable) -> None:
  50. self.app = app
  51. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  52. assert scope["type"] == "http"
  53. responder = WSGIResponder(self.app, scope)
  54. await responder(receive, send)
  55. class WSGIResponder:
  56. def __init__(self, app: typing.Callable, scope: Scope) -> None:
  57. self.app = app
  58. self.scope = scope
  59. self.status = None
  60. self.response_headers = None
  61. self.stream_send, self.stream_receive = anyio.create_memory_object_stream(
  62. math.inf
  63. )
  64. self.response_started = False
  65. self.exc_info: typing.Any = None
  66. async def __call__(self, receive: Receive, send: Send) -> None:
  67. body = b""
  68. more_body = True
  69. while more_body:
  70. message = await receive()
  71. body += message.get("body", b"")
  72. more_body = message.get("more_body", False)
  73. environ = build_environ(self.scope, body)
  74. async with anyio.create_task_group() as task_group:
  75. task_group.start_soon(self.sender, send)
  76. async with self.stream_send:
  77. await anyio.to_thread.run_sync(self.wsgi, environ, self.start_response)
  78. if self.exc_info is not None:
  79. raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
  80. async def sender(self, send: Send) -> None:
  81. async with self.stream_receive:
  82. async for message in self.stream_receive:
  83. await send(message)
  84. def start_response(
  85. self,
  86. status: str,
  87. response_headers: typing.List[typing.Tuple[str, str]],
  88. exc_info: typing.Any = None,
  89. ) -> None:
  90. self.exc_info = exc_info
  91. if not self.response_started:
  92. self.response_started = True
  93. status_code_string, _ = status.split(" ", 1)
  94. status_code = int(status_code_string)
  95. headers = [
  96. (name.strip().encode("ascii").lower(), value.strip().encode("ascii"))
  97. for name, value in response_headers
  98. ]
  99. anyio.from_thread.run(
  100. self.stream_send.send,
  101. {
  102. "type": "http.response.start",
  103. "status": status_code,
  104. "headers": headers,
  105. },
  106. )
  107. def wsgi(self, environ: dict, start_response: typing.Callable) -> None:
  108. for chunk in self.app(environ, start_response):
  109. anyio.from_thread.run(
  110. self.stream_send.send,
  111. {"type": "http.response.body", "body": chunk, "more_body": True},
  112. )
  113. anyio.from_thread.run(
  114. self.stream_send.send, {"type": "http.response.body", "body": b""}
  115. )