summaryrefslogtreecommitdiffstats
path: root/venv/lib/python3.9/site-packages/tornado/wsgi.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.9/site-packages/tornado/wsgi.py')
-rw-r--r--venv/lib/python3.9/site-packages/tornado/wsgi.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/tornado/wsgi.py b/venv/lib/python3.9/site-packages/tornado/wsgi.py
new file mode 100644
index 00000000..32641be3
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/tornado/wsgi.py
@@ -0,0 +1,268 @@
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""WSGI support for the Tornado web framework.
+
+WSGI is the Python standard for web servers, and allows for interoperability
+between Tornado and other Python web frameworks and servers.
+
+This module provides WSGI support via the `WSGIContainer` class, which
+makes it possible to run applications using other WSGI frameworks on
+the Tornado HTTP server. The reverse is not supported; the Tornado
+`.Application` and `.RequestHandler` classes are designed for use with
+the Tornado `.HTTPServer` and cannot be used in a generic WSGI
+container.
+
+"""
+
+import concurrent.futures
+from io import BytesIO
+import tornado
+import sys
+
+from tornado.concurrent import dummy_executor
+from tornado import escape
+from tornado import httputil
+from tornado.ioloop import IOLoop
+from tornado.log import access_log
+
+from typing import List, Tuple, Optional, Callable, Any, Dict, Text
+from types import TracebackType
+import typing
+
+if typing.TYPE_CHECKING:
+ from typing import Type # noqa: F401
+ from _typeshed.wsgi import WSGIApplication as WSGIAppType # noqa: F401
+
+
+# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
+# that are smuggled inside objects of type unicode (via the latin1 encoding).
+# This function is like those in the tornado.escape module, but defined
+# here to minimize the temptation to use it in non-wsgi contexts.
+def to_wsgi_str(s: bytes) -> str:
+ assert isinstance(s, bytes)
+ return s.decode("latin1")
+
+
+class WSGIContainer(object):
+ r"""Makes a WSGI-compatible application runnable on Tornado's HTTP server.
+
+ .. warning::
+
+ WSGI is a *synchronous* interface, while Tornado's concurrency model
+ is based on single-threaded *asynchronous* execution. Many of Tornado's
+ distinguishing features are not available in WSGI mode, including efficient
+ long-polling and websockets. The primary purpose of `WSGIContainer` is
+ to support both WSGI applications and native Tornado ``RequestHandlers`` in
+ a single process. WSGI-only applications are likely to be better off
+ with a dedicated WSGI server such as ``gunicorn`` or ``uwsgi``.
+
+ Wrap a WSGI application in a `WSGIContainer` to make it implement the Tornado
+ `.HTTPServer` ``request_callback`` interface. The `WSGIContainer` object can
+ then be passed to classes from the `tornado.routing` module,
+ `tornado.web.FallbackHandler`, or to `.HTTPServer` directly.
+
+ This class is intended to let other frameworks (Django, Flask, etc)
+ run on the Tornado HTTP server and I/O loop.
+
+ Realistic usage will be more complicated, but the simplest possible example uses a
+ hand-written WSGI application with `.HTTPServer`::
+
+ def simple_app(environ, start_response):
+ status = "200 OK"
+ response_headers = [("Content-type", "text/plain")]
+ start_response(status, response_headers)
+ return [b"Hello world!\n"]
+
+ async def main():
+ container = tornado.wsgi.WSGIContainer(simple_app)
+ http_server = tornado.httpserver.HTTPServer(container)
+ http_server.listen(8888)
+ await asyncio.Event().wait()
+
+ asyncio.run(main())
+
+ The recommended pattern is to use the `tornado.routing` module to set up routing
+ rules between your WSGI application and, typically, a `tornado.web.Application`.
+ Alternatively, `tornado.web.Application` can be used as the top-level router
+ and `tornado.web.FallbackHandler` can embed a `WSGIContainer` within it.
+
+ If the ``executor`` argument is provided, the WSGI application will be executed
+ on that executor. This must be an instance of `concurrent.futures.Executor`,
+ typically a ``ThreadPoolExecutor`` (``ProcessPoolExecutor`` is not supported).
+ If no ``executor`` is given, the application will run on the event loop thread in
+ Tornado 6.3; this will change to use an internal thread pool by default in
+ Tornado 7.0.
+
+ .. warning::
+ By default, the WSGI application is executed on the event loop's thread. This
+ limits the server to one request at a time (per process), making it less scalable
+ than most other WSGI servers. It is therefore highly recommended that you pass
+ a ``ThreadPoolExecutor`` when constructing the `WSGIContainer`, after verifying
+ that your application is thread-safe. The default will change to use a
+ ``ThreadPoolExecutor`` in Tornado 7.0.
+
+ .. versionadded:: 6.3
+ The ``executor`` parameter.
+
+ .. deprecated:: 6.3
+ The default behavior of running the WSGI application on the event loop thread
+ is deprecated and will change in Tornado 7.0 to use a thread pool by default.
+ """
+
+ def __init__(
+ self,
+ wsgi_application: "WSGIAppType",
+ executor: Optional[concurrent.futures.Executor] = None,
+ ) -> None:
+ self.wsgi_application = wsgi_application
+ self.executor = dummy_executor if executor is None else executor
+
+ def __call__(self, request: httputil.HTTPServerRequest) -> None:
+ IOLoop.current().spawn_callback(self.handle_request, request)
+
+ async def handle_request(self, request: httputil.HTTPServerRequest) -> None:
+ data = {} # type: Dict[str, Any]
+ response = [] # type: List[bytes]
+
+ def start_response(
+ status: str,
+ headers: List[Tuple[str, str]],
+ exc_info: Optional[
+ Tuple[
+ "Optional[Type[BaseException]]",
+ Optional[BaseException],
+ Optional[TracebackType],
+ ]
+ ] = None,
+ ) -> Callable[[bytes], Any]:
+ data["status"] = status
+ data["headers"] = headers
+ return response.append
+
+ loop = IOLoop.current()
+ app_response = await loop.run_in_executor(
+ self.executor,
+ self.wsgi_application,
+ self.environ(request),
+ start_response,
+ )
+ try:
+ app_response_iter = iter(app_response)
+
+ def next_chunk() -> Optional[bytes]:
+ try:
+ return next(app_response_iter)
+ except StopIteration:
+ # StopIteration is special and is not allowed to pass through
+ # coroutines normally.
+ return None
+
+ while True:
+ chunk = await loop.run_in_executor(self.executor, next_chunk)
+ if chunk is None:
+ break
+ response.append(chunk)
+ finally:
+ if hasattr(app_response, "close"):
+ app_response.close() # type: ignore
+ body = b"".join(response)
+ if not data:
+ raise Exception("WSGI app did not call start_response")
+
+ status_code_str, reason = data["status"].split(" ", 1)
+ status_code = int(status_code_str)
+ headers = data["headers"] # type: List[Tuple[str, str]]
+ header_set = set(k.lower() for (k, v) in headers)
+ body = escape.utf8(body)
+ if status_code != 304:
+ if "content-length" not in header_set:
+ headers.append(("Content-Length", str(len(body))))
+ if "content-type" not in header_set:
+ headers.append(("Content-Type", "text/html; charset=UTF-8"))
+ if "server" not in header_set:
+ headers.append(("Server", "TornadoServer/%s" % tornado.version))
+
+ start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
+ header_obj = httputil.HTTPHeaders()
+ for key, value in headers:
+ header_obj.add(key, value)
+ assert request.connection is not None
+ request.connection.write_headers(start_line, header_obj, chunk=body)
+ request.connection.finish()
+ self._log(status_code, request)
+
+ def environ(self, request: httputil.HTTPServerRequest) -> Dict[Text, Any]:
+ """Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.
+
+ .. versionchanged:: 6.3
+ No longer a static method.
+ """
+ hostport = request.host.split(":")
+ if len(hostport) == 2:
+ host = hostport[0]
+ port = int(hostport[1])
+ else:
+ host = request.host
+ port = 443 if request.protocol == "https" else 80
+ environ = {
+ "REQUEST_METHOD": request.method,
+ "SCRIPT_NAME": "",
+ "PATH_INFO": to_wsgi_str(
+ escape.url_unescape(request.path, encoding=None, plus=False)
+ ),
+ "QUERY_STRING": request.query,
+ "REMOTE_ADDR": request.remote_ip,
+ "SERVER_NAME": host,
+ "SERVER_PORT": str(port),
+ "SERVER_PROTOCOL": request.version,
+ "wsgi.version": (1, 0),
+ "wsgi.url_scheme": request.protocol,
+ "wsgi.input": BytesIO(escape.utf8(request.body)),
+ "wsgi.errors": sys.stderr,
+ "wsgi.multithread": self.executor is not dummy_executor,
+ "wsgi.multiprocess": True,
+ "wsgi.run_once": False,
+ }
+ if "Content-Type" in request.headers:
+ environ["CONTENT_TYPE"] = request.headers.pop("Content-Type")
+ if "Content-Length" in request.headers:
+ environ["CONTENT_LENGTH"] = request.headers.pop("Content-Length")
+ for key, value in request.headers.items():
+ environ["HTTP_" + key.replace("-", "_").upper()] = value
+ return environ
+
+ def _log(self, status_code: int, request: httputil.HTTPServerRequest) -> None:
+ if status_code < 400:
+ log_method = access_log.info
+ elif status_code < 500:
+ log_method = access_log.warning
+ else:
+ log_method = access_log.error
+ request_time = 1000.0 * request.request_time()
+ assert request.method is not None
+ assert request.uri is not None
+ summary = (
+ request.method # type: ignore[operator]
+ + " "
+ + request.uri
+ + " ("
+ + request.remote_ip
+ + ")"
+ )
+ log_method("%d %s %.2fms", status_code, summary, request_time)
+
+
+HTTPRequest = httputil.HTTPServerRequest