您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

134 行
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. )