aiohttp-wsgi_0.10.0.orig/.github/0000755000000000000000000000000014240147630013517 5ustar00aiohttp-wsgi_0.10.0.orig/.gitignore0000644000000000000000000000015713533255766014170 0ustar00.DS_Store *.pyc *.pyo __pycache__ Thumbs.db /venv .coverage *.log *.egg-info /dist /build /.cache /docs/_build aiohttp-wsgi_0.10.0.orig/LICENSE0000644000000000000000000000276413533255766013213 0ustar00Copyright (c) 2015, David Hall. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of David Hall nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aiohttp-wsgi_0.10.0.orig/README.rst0000644000000000000000000000174114240147630013651 0ustar00aiohttp-wsgi ============ WSGI adapter for `aiohttp `_. **aiohttp-wsgi** is actively maintained and used in prodution, with any bug reports or feature requests answered promptly. It's a small, stable library, so can go a long time without new releases or updates! Features -------- - Run WSGI applications (e.g. `Django `_, `Flask `_) on `aiohttp `_. - Handle thousands of client connections, using `asyncio `_. - Add `websockets `_ to your existing Python web app! Resources --------- - `Documentation `_ is on Read the Docs. - `Issue tracking `_ and `source code `_ is on GitHub. aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/0000755000000000000000000000000013533255766014676 5ustar00aiohttp-wsgi_0.10.0.orig/docs/0000755000000000000000000000000013533255766013125 5ustar00aiohttp-wsgi_0.10.0.orig/readthedocs.yml0000644000000000000000000000007414240147630015170 0ustar00version: 2 python: version: 3.6 install: - path: . aiohttp-wsgi_0.10.0.orig/setup.cfg0000644000000000000000000000115214240147630013777 0ustar00[flake8] max-line-length=120 exclude=venv ignore=E306 [coverage:run] source = aiohttp_wsgi tests omit = **/__main__.py [coverage:report] exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError assert False show_missing = True skip_covered = True [mypy] files = aiohttp_wsgi, tests warn_redundant_casts = True warn_unused_ignores = True allow_redefinition = True disallow_untyped_calls = True disallow_untyped_defs = True disallow_incomplete_defs = True aiohttp-wsgi_0.10.0.orig/setup.py0000644000000000000000000000224214240147630013671 0ustar00from setuptools import setup, find_packages import aiohttp_wsgi setup( name="aiohttp-wsgi", version=aiohttp_wsgi.__version__, license="BSD", description="WSGI adapter for aiohttp.", author="Dave Hall", author_email="dave@etianen.com", url="https://github.com/etianen/aiohttp-wsgi", packages=find_packages(exclude=("tests",)), package_data={"aiohttp_wsgi": ["py.typed"]}, install_requires=[ "aiohttp>=3.4,<4", ], entry_points={ "console_scripts": ["aiohttp-wsgi-serve=aiohttp_wsgi.__main__:main"], }, classifiers=[ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Framework :: Django", ], ) aiohttp-wsgi_0.10.0.orig/tests/0000755000000000000000000000000013533255766013337 5ustar00aiohttp-wsgi_0.10.0.orig/.github/workflows/0000755000000000000000000000000014240147630015554 5ustar00aiohttp-wsgi_0.10.0.orig/.github/workflows/python-package.yml0000644000000000000000000000210614240147630021210 0ustar00name: Python package on: [push, pull_request] jobs: build: runs-on: ubuntu-latest env: PYTHONDEVMODE: 1 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install flake8 mypy coverage sphinx -e . - name: Lint with flake8 run: | flake8 - name: Check with mypy run: | mypy --no-incremental --warn-unused-configs - name: Test with unittest run: | coverage run -m unittest discover tests # Since we generate dynamic docstrings, ensure nothing crashes when they're stripped out in optimize mode. PYTHONOPTIMIZE=2 python -m unittest discover tests coverage report --fail-under=100 - name: Build docs run: | (cd docs && sphinx-build -W . _build) aiohttp-wsgi_0.10.0.orig/.github/workflows/python-publish.yml0000644000000000000000000000115314240147630021264 0ustar00name: Upload Python Package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python setup.py sdist bdist_wheel twine upload dist/* aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/__init__.py0000644000000000000000000000172514240147630016776 0ustar00""" aiohttp-wsgi ============ WSGI adapter for :ref:`aiohttp `. Features -------- - Run WSGI applications (e.g. `Django`_, `Flask`_) on :ref:`aiohttp `. - Handle thousands of client connections, using :mod:`asyncio`. - Add :ref:`websockets ` to your existing Python web app! Resources --------- - `Documentation`_ is on Read the Docs. - `Issue tracking`_ and `source code`_ is on GitHub. Usage ----- .. toctree:: :maxdepth: 1 installation wsgi main More information ---------------- .. toctree:: :maxdepth: 1 contributing changelog .. include:: /_include/links.rst """ try: import aiohttp # noqa except ImportError: # pragma: no cover # The top-level API requires aiohttp, which might not be present if setup.py # is importing aiohttp_wsgi to get __version__. pass else: from aiohttp_wsgi.wsgi import WSGIHandler, serve # noqa __version__ = "0.10.0" aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/__main__.py0000644000000000000000000001045614240147630016760 0ustar00""" Command line interface (CLI) ============================ If you don't need to add :ref:`websockets ` or :ref:`async request handlers ` to your app, but still want to run your WSGI app on the :mod:`asyncio` event loop, :mod:`aiohttp_wsgi` provides a simple command line interface. Example usage ------------- Serve a WSGI application called ``application``, located in the ``your_project.wsgi`` module: .. code:: bash aiohttp-wsgi-serve your_project.wsgi:application Serve a WSGI application and include a static file directory. .. code:: bash aiohttp-wsgi-serve your_project.wsgi:application --static /static=./static Command reference ----------------- You can view this reference at any time with ``aiohttp-wsgi-serve --help``. .. code:: bash {help} .. include:: /_include/links.rst """ import argparse import logging import os import sys from importlib import import_module from typing import Any, Callable, Tuple import aiohttp_wsgi from aiohttp_wsgi.wsgi import serve, DEFAULTS, HELP logger = logging.getLogger(__name__) parser = argparse.ArgumentParser( prog="aiohttp-wsgi-serve", description="Run a WSGI application.", ) def add_argument(name: str, *aliases: str, **kwargs: Any) -> None: varname = name.strip("-").replace("-", "_") # Format help. kwargs.setdefault("help", HELP.get(varname, "").replace("``", "")) assert kwargs["help"] # Parse action. kwargs.setdefault("action", "store") if kwargs["action"] in ("append", "count"): kwargs["help"] += " Can be specified multiple times." if kwargs["action"] == "count": kwargs.setdefault("default", 0) if kwargs["action"] in ("append", "store"): kwargs.setdefault("default", DEFAULTS.get(varname)) kwargs.setdefault("type", type(kwargs["default"])) assert not isinstance(None, kwargs["type"]) parser.add_argument(name, *aliases, **kwargs) add_argument( "application", metavar="module:application", type=str, ) add_argument( "--host", type=str, action="append", ) add_argument( "--port", "-p", ) add_argument( "--unix-socket", type=str, ) add_argument( "--unix-socket-perms", ) add_argument( "--backlog", ) add_argument( "--static", action="append", default=[], type=str, help=( "Static route mappings in the form 'path=directory'. " "`path` must start with a slash, but not end with a slash." ), ) add_argument( "--static-cors", type=str, ) add_argument( "--script-name", ) add_argument( "--url-scheme", type=str, ) add_argument( "--threads", ) add_argument( "--inbuf-overflow", ) add_argument( "--max-request-body-size", ) add_argument( "--shutdown-timeout", ) add_argument( "--verbose", "-v", action="count", help="Increase verbosity.", ) add_argument( "--quiet", "-q", action="count", help="Decrease verbosity.", ) add_argument( "--version", action="version", help="Display version information.", version=f"aiohttp-wsgi v{aiohttp_wsgi.__version__}", ) def import_func(func: str) -> Callable: assert ":" in func, f"{func!r} should have format 'module:callable'" module_name, func_name = func.split(":", 1) module = import_module(module_name) func = getattr(module, func_name) return func def parse_static_item(static_item: str) -> Tuple[str, str]: assert "=" in static_item, f"{static_item!r} should have format 'path=directory'" return tuple(static_item.split("=", 1)) # type: ignore def main() -> None: sys.path.insert(0, os.getcwd()) # Parse the args. kwargs = vars(parser.parse_args(sys.argv[1:])) application = import_func(kwargs.pop("application")) static = list(map(parse_static_item, kwargs.pop("static"))) # Set up logging. verbosity = (kwargs.pop("verbose") - kwargs.pop("quiet")) * 10 logging.basicConfig(level=max(logging.ERROR - verbosity, logging.DEBUG), format="%(message)s") logging.getLogger("aiohttp").setLevel(max(logging.INFO - verbosity, logging.DEBUG)) logger.setLevel(max(logging.INFO - verbosity, logging.DEBUG)) # Serve! serve(application, static=static, **kwargs) if __debug__: import textwrap __doc__ = __doc__.format(help=textwrap.indent(parser.format_help(), " "), **HELP) aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/py.typed0000644000000000000000000000000014240147630016345 0ustar00aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/utils.py0000644000000000000000000000032314240147630016370 0ustar00from typing import Tuple, Union def parse_sockname(sockname: Union[Tuple, str]) -> Tuple[str, str]: if isinstance(sockname, tuple): return sockname[0], str(sockname[1]) return "unix", sockname aiohttp-wsgi_0.10.0.orig/aiohttp_wsgi/wsgi.py0000644000000000000000000004475314240147630016220 0ustar00""" Running a WSGI app ================== .. currentmodule:: aiohttp_wsgi :mod:`aiohttp_wsgi` allows you to run WSGI applications (e.g. `Django`_, `Flask`_) on :ref:`aiohttp `. This allows you to add async features like websockets and long-polling to an existing Python web app. .. hint:: If you don't need to add :ref:`websockets ` or :ref:`async request handlers ` to your app, but still want to run your WSGI app on the :mod:`asyncio` event loop, :mod:`aiohttp_wsgi` provides a simpler :doc:`command line interface
`. Run a web server ---------------- In order to implement a WSGI server, first import your WSGI application and wrap it in a :class:`WSGIHandler`. .. code:: python from aiohttp import web from aiohttp_wsgi import WSGIHandler from your_project.wsgi import application wsgi_handler = WSGIHandler(application) Next, create an :class:`Application ` instance and register the request handler with the application's :class:`router ` on a particular HTTP *method* and *path*: .. code:: python app = web.Application() app.router.add_route("*", "/{path_info:.*}", wsgi_handler) After that, run the application by :func:`run_app() ` call: .. code:: python web.run_app(app) See the :ref:`aiohttp.web ` documentation for information on adding :ref:`websockets ` and :ref:`async request handlers ` to your app. Serving simple WSGI apps ------------------------ If you don't need to add :ref:`websockets ` or :ref:`async request handlers ` to your app, but still want to run your WSGI app on the :mod:`asyncio` event loop, :mod:`aiohttp_wsgi` provides a simple :func:`serve()` helper. .. code:: python from aiohttp_wsgi import serve serve(application) Extra environ keys ------------------ :mod:`aiohttp_wsgi` adds the following additional keys to the WSGI environ: ``asyncio.executor`` The :class:`Executor ` running the WSGI request. ``aiohttp.request`` The raw :class:`aiohttp.web.Request` that initiated the WSGI request. Use this to access additional request :ref:`metadata `. API reference ------------- .. autoclass:: WSGIHandler :members: .. autofunction:: serve .. include:: /_include/links.rst """ import asyncio from asyncio.base_events import Server from functools import partial from io import BytesIO import logging import os import sys from concurrent.futures import Executor, ThreadPoolExecutor from contextlib import contextmanager from tempfile import SpooledTemporaryFile from typing import Any, Awaitable, IO, Callable, Dict, Generator, Iterable, List, Optional, Tuple from wsgiref.util import is_hop_by_hop from aiohttp.web import ( Application, AppRunner, BaseSite, TCPSite, UnixSite, Request, Response, StreamResponse, HTTPRequestEntityTooLarge, middleware, ) from aiohttp.web_response import CIMultiDict from aiohttp_wsgi.utils import parse_sockname WSGIEnviron = Dict[str, Any] WSGIHeaders = List[Tuple[str, str]] WSGIAppendResponse = Callable[[bytes], None] WSGIStartResponse = Callable[[str, WSGIHeaders], Callable[[bytes], None]] WSGIApplication = Callable[[WSGIEnviron, WSGIStartResponse], Iterable[bytes]] logger = logging.getLogger(__name__) def _run_application(application: WSGIApplication, environ: WSGIEnviron) -> Response: # Response data. response_status: Optional[int] = None response_reason: Optional[str] = None response_headers: Optional[WSGIHeaders] = None response_body: List[bytes] = [] # Simple start_response callable. def start_response(status: str, headers: WSGIHeaders, exc_info: Optional[Exception] = None) -> WSGIAppendResponse: nonlocal response_status, response_reason, response_headers, response_body status_code, reason = status.split(None, 1) status_code = int(status_code) # Check the headers. if __debug__: for header_name, header_value in headers: assert not is_hop_by_hop(header_name), f"hop-by-hop headers are forbidden: {header_name}" # Start the response. response_status = status_code response_reason = reason response_headers = headers del response_body[:] return response_body.append # Run the application. body_iterable = application(environ, start_response) try: response_body.extend(body_iterable) assert ( response_status is not None and response_reason is not None and response_headers is not None ), "application did not call start_response()" return Response( status=response_status, reason=response_reason, headers=CIMultiDict(response_headers), body=b"".join(response_body), ) finally: # Close the body. if hasattr(body_iterable, "close"): body_iterable.close() # type: ignore class WSGIHandler: """ An adapter for WSGI applications, allowing them to run on :ref:`aiohttp `. :param application: {application} :param str url_scheme: {url_scheme} :param io.BytesIO stderr: {stderr} :param int inbuf_overflow: {inbuf_overflow} :param int max_request_body_size: {max_request_body_size} :param concurrent.futures.Executor executor: {executor} """ def __init__( self, application: WSGIApplication, *, # Handler config. url_scheme: Optional[str] = None, stderr: Optional[IO[bytes]] = None, inbuf_overflow: int = 524288, max_request_body_size: int = 1073741824, # asyncio config. executor: Optional[Executor] = None, ): assert callable(application), "application should be callable" self._application = application # Handler config. self._url_scheme = url_scheme self._stderr = stderr or sys.stderr assert isinstance(inbuf_overflow, int), "inbuf_overflow should be int" assert inbuf_overflow >= 0, "inbuf_overflow should be >= 0" assert isinstance(max_request_body_size, int), "max_request_body_size should be int" assert max_request_body_size >= 0, "max_request_body_size should be >= 0" if inbuf_overflow < max_request_body_size: self._body_io: Callable[[], IO[bytes]] = partial(SpooledTemporaryFile, max_size=inbuf_overflow) else: # Use BytesIO as an optimization if we'll never overflow to disk. self._body_io = BytesIO self._max_request_body_size = max_request_body_size # asyncio config. self._executor = executor def _get_environ(self, request: Request, body: IO[bytes], content_length: int) -> WSGIEnviron: # Resolve the path info. path_info = request.match_info["path_info"] script_name = request.rel_url.path[:len(request.rel_url.path) - len(path_info)] # Special case: If the app was mounted on the root, then the script name will # currently be set to "/", which is illegal in the WSGI spec. The script name # could also end with a slash if the WSGIHandler was mounted as a route # manually with a trailing slash before the path_info. In either case, we # correct this according to the WSGI spec by transferring the trailing slash # from script_name to the start of path_info. if script_name.endswith("/"): script_name = script_name[:-1] path_info = "/" + path_info # Parse the connection info. assert request.transport is not None server_name, server_port = parse_sockname(request.transport.get_extra_info("sockname")) remote_addr, remote_port = parse_sockname(request.transport.get_extra_info("peername")) # Detect the URL scheme. url_scheme = self._url_scheme if url_scheme is None: url_scheme = "http" if request.transport.get_extra_info("sslcontext") is None else "https" # Create the environ. environ = { "REQUEST_METHOD": request.method, "SCRIPT_NAME": script_name, "PATH_INFO": path_info, "RAW_URI": request.raw_path, # RAW_URI: Gunicorn's non-standard field "REQUEST_URI": request.raw_path, # REQUEST_URI: uWSGI/Apache mod_wsgi's non-standard field "QUERY_STRING": request.rel_url.raw_query_string, "CONTENT_TYPE": request.headers.get("Content-Type", ""), "CONTENT_LENGTH": str(content_length), "SERVER_NAME": server_name, "SERVER_PORT": server_port, "REMOTE_ADDR": remote_addr, "REMOTE_HOST": remote_addr, "REMOTE_PORT": remote_port, "SERVER_PROTOCOL": "HTTP/{}.{}".format(*request.version), "wsgi.version": (1, 0), "wsgi.url_scheme": url_scheme, "wsgi.input": body, "wsgi.errors": self._stderr, "wsgi.multithread": True, "wsgi.multiprocess": False, "wsgi.run_once": False, "asyncio.executor": self._executor, "aiohttp.request": request, } # Add in additional HTTP headers. for header_name in request.headers: header_name = header_name.upper() if not(is_hop_by_hop(header_name)) and header_name not in ("CONTENT-LENGTH", "CONTENT-TYPE"): header_value = ",".join(request.headers.getall(header_name)) environ["HTTP_" + header_name.replace("-", "_")] = header_value # All done! return environ async def handle_request(self, request: Request) -> Response: # Check for body size overflow. if request.content_length is not None and request.content_length > self._max_request_body_size: raise HTTPRequestEntityTooLarge( max_size=self._max_request_body_size, actual_size=request.content_length, ) # Buffer the body. content_length = 0 with self._body_io() as body: while True: block = await request.content.readany() if not block: break content_length += len(block) if content_length > self._max_request_body_size: raise HTTPRequestEntityTooLarge( max_size=self._max_request_body_size, actual_size=content_length, ) body.write(block) body.seek(0) # Get the environ. environ = self._get_environ(request, body, content_length) loop = asyncio.get_event_loop() return await loop.run_in_executor( self._executor, _run_application, self._application, environ, ) __call__ = handle_request def format_path(path: str) -> str: assert not path.endswith("/"), f"{path!r} name should not end with /" if path == "": path = "/" assert path.startswith("/"), f"{path!r} name should start with /" return path Handler = Callable[[Request], Awaitable[StreamResponse]] Middleware = Callable[[Request, Handler], Awaitable[StreamResponse]] def static_cors_middleware(*, static: Iterable[Tuple[str, str]], static_cors: str) -> Middleware: @middleware async def do_static_cors_middleware(request: Request, handler: Handler) -> StreamResponse: response = await handler(request) for path, _ in static: if request.path.startswith(path): response.headers["Access-Control-Allow-Origin"] = static_cors break return response return do_static_cors_middleware @contextmanager def run_server( application: WSGIApplication, *, # asyncio config. threads: int = 4, # Server config. host: Optional[str] = None, port: int = 8080, # Unix server config. unix_socket: Optional[str] = None, unix_socket_perms: int = 0o600, # Shared server config. backlog: int = 1024, # aiohttp config. static: Iterable[Tuple[str, str]] = (), static_cors: Optional[str] = None, script_name: str = "", shutdown_timeout: float = 60.0, **kwargs: Any, ) -> Generator[Tuple[asyncio.AbstractEventLoop, BaseSite], None, None]: # Set up async context. loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) assert threads >= 1, "threads should be >= 1" executor = ThreadPoolExecutor(threads) # Create aiohttp app. app = Application() # Add static routes. static = [(format_path(path), dirname) for path, dirname in static] for path, dirname in static: app.router.add_static(path, dirname) # Add the wsgi application. This has to be last. app.router.add_route( "*", f"{format_path(script_name)}{{path_info:.*}}", WSGIHandler( application, executor=executor, **kwargs ).handle_request, ) # Configure middleware. if static_cors: app.middlewares.append(static_cors_middleware( static=static, static_cors=static_cors, )) # Start the app runner. runner = AppRunner(app) loop.run_until_complete(runner.setup()) # Set up the server. if unix_socket is not None: site: BaseSite = UnixSite(runner, path=unix_socket, backlog=backlog, shutdown_timeout=shutdown_timeout) else: site = TCPSite(runner, host=host, port=port, backlog=backlog, shutdown_timeout=shutdown_timeout) loop.run_until_complete(site.start()) # Set socket permissions. if unix_socket is not None: os.chmod(unix_socket, unix_socket_perms) # Report. assert site._server is not None assert isinstance(site._server, Server) assert site._server.sockets is not None server_uri = " ".join( "http://{}:{}".format(*parse_sockname(socket.getsockname())) for socket in site._server.sockets ) logger.info("Serving on %s", server_uri) try: yield loop, site finally: # Clean up unix sockets. for socket in site._server.sockets: sock_host, sock_port = parse_sockname(socket.getsockname()) if sock_host == "unix": os.unlink(sock_port) # Close the server. logger.debug("Shutting down server on %s", server_uri) loop.run_until_complete(site.stop()) # Shut down app. logger.debug("Shutting down app on %s", server_uri) loop.run_until_complete(runner.cleanup()) # Shut down executor. logger.debug("Shutting down executor on %s", server_uri) executor.shutdown() # Shut down loop. logger.debug("Shutting down loop on %s", server_uri) loop.close() asyncio.set_event_loop(None) # All done! logger.info("Stopped serving on %s", server_uri) def serve(application: WSGIApplication, **kwargs: Any) -> None: # pragma: no cover """ Runs the WSGI application on :ref:`aiohttp `, serving it until keyboard interrupt. :param application: {application} :param str url_scheme: {url_scheme} :param io.BytesIO stderr: {stderr} :param int inbuf_overflow: {inbuf_overflow} :param int max_request_body_size: {max_request_body_size} :param int threads: {threads} :param str host: {host} :param int port: {port} :param str unix_socket: {unix_socket} :param int unix_socket_perms: {unix_socket_perms} :param int backlog: {backlog} :param list static: {static} :param list static_cors: {static_cors} :param str script_name: {script_name} :param int shutdown_timeout: {shutdown_timeout} """ with run_server(application, **kwargs) as (loop, site): try: loop.run_forever() except KeyboardInterrupt: pass DEFAULTS = {} DEFAULTS.update(WSGIHandler.__init__.__kwdefaults__) # type: ignore DEFAULTS.update(run_server.__wrapped__.__kwdefaults__) # type: ignore HELP = { "application": "A WSGI application callable.", "url_scheme": ( "A hint about the URL scheme used to access the application. Corresponds to ``environ['wsgi.url_scheme']``. " "Default is auto-detected to ``'http'`` or ``'https'``." ), "stderr": ( "A file-like value for WSGI error logging. Corresponds to ``environ['wsgi.errors']``. " "Defaults to ``sys.stderr``." ), "inbuf_overflow": ( "A tempfile will be created if the request body is larger than this value, which is measured in bytes. " "Defaults to ``{inbuf_overflow!r}``." ).format_map(DEFAULTS), "max_request_body_size": ( "Maximum number of bytes in request body. Defaults to ``{max_request_body_size!r}``. " "Larger requests will receive a HTTP 413 (Request Entity Too Large) response." ).format_map(DEFAULTS), "executor": "An Executor instance used to run WSGI requests. Defaults to the :mod:`asyncio` base executor.", "host": "Host interfaces to bind. Defaults to ``'0.0.0.0'`` and ``'::'``.", "port": "Port to bind. Defaults to ``{port!r}``.".format_map(DEFAULTS), "unix_socket": "Path to a unix socket to bind, cannot be used with ``host``.", "unix_socket_perms": ( "Filesystem permissions to apply to the unix socket. Defaults to ``{unix_socket_perms!r}``." ).format_map(DEFAULTS), "backlog": "Socket connection backlog. Defaults to {backlog!r}.".format_map(DEFAULTS), "static": "Static root mappings in the form (path, directory). Defaults to {static!r}".format_map(DEFAULTS), "static_cors": ( "Set to '*' to enable CORS on static files for all origins, or a string to enable CORS for a specific origin. " "Defaults to {static_cors!r}" ).format_map(DEFAULTS), "script_name": ( "URL prefix for the WSGI application, should start with a slash, but not end with a slash. " "Defaults to ``{script_name!r}``." ).format_map(DEFAULTS), "threads": "Number of threads used to process application logic. Defaults to ``{threads!r}``.".format_map(DEFAULTS), "shutdown_timeout": ( "Timeout when closing client connections on server shutdown. Defaults to ``{shutdown_timeout!r}``." ).format_map(DEFAULTS), } if __debug__: assert WSGIHandler.__doc__ is not None WSGIHandler.__doc__ = WSGIHandler.__doc__.format_map(HELP) assert serve.__doc__ is not None serve.__doc__ = serve.__doc__.format_map(HELP) aiohttp-wsgi_0.10.0.orig/docs/_include/0000755000000000000000000000000013533255766014707 5ustar00aiohttp-wsgi_0.10.0.orig/docs/changelog.rst0000644000000000000000000001175314240147630015577 0ustar00aiohttp-wsgi changelog ====================== .. currentmodule:: aiohttp_wsgi 0.10.0 ------ - Removed ``loop`` argument from :class:`WSGIHandler` constructor. - Fixed tests to work with aiohttp 3.8.1+. 0.9.1 ----- - **Bugfix:** Added in ``py.typed`` PEP 561 marker file. 0.9.0 ----- - Added PEP 484 / PEP 561 type annotations. - **Bugfix:** Fixed crash when running with ``PYTHONOPTIMIZE=2``. 0.8.2 ----- - Added support for ``aiohttp >= 3.4`` (@michael-k). 0.8.1 ----- - Added ``static_cors`` argument to :func:`serve()`, allowing CORS to be configured for static files. - Added ``--static-cors`` argument to :doc:`aiohttp-wsgi-serve
` command line interface. 0.8.0 ----- - Added new :func:`serve()` helper for simple WSGI applications that don't need :ref:`websockets ` or :ref:`async request handlers ` (@etianen). - Updated :mod:`aiohttp` dependency to ``>=3`` (@etianen). - Improved error message for invalid hop-by-hop headers (@chriskuehl). - **Breaking:** Dropped support for Python 3.4 (@etianen). 0.7.1 ----- - Compatibility with aiohttp>=2.3.1 (@etianen). 0.7.0 ----- - Compatibility with aiohttp>=2 (@etianen). - Added ``"RAW_URI"`` and ``"REQUEST_URI"`` keys to the environ dict, allowing the original quoted path to be accessed (@dahlia). 0.6.6 ----- - Python 3.4 support (@fscherf). 0.6.5 ----- - Fixed bug with unicode errors in querystring (@Антон Огородников). 0.6.4 ----- - Updating aiohttp dependency to >= 1.2. - Fixing aiohttp deprecation warnings. 0.6.3 ----- - Updating aiohttp dependency to >= 1.0. 0.6.2 ----- - Fixing incorrect quoting of ``PATH_INFO`` and ``SCRIPT_NAME`` in environ. 0.6.1 ----- - Upgrading :mod:`aiohttp` dependency to >= 0.22.2. 0.6.0 ----- - Fixing missing multiple headers sent from start_response. - **Breaking:** Removed outbuf_overflow setting. Responses are always buffered in memory. - **Breaking:** WSGI streaming responses are buffered fully in memory before being sent. 0.5.2 ----- - Identical to 0.5.1, after PyPi release proved mysteriously broken. 0.5.1 ----- - ``outbuf_overflow`` no longer creates a temporary buffer file, instead pausing the worker thread until the pending response has been flushed. 0.5.0 ----- - Minimum :ref:`aiohttp ` version is now 0.21.2. - Added :doc:`aiohttp-wsgi-serve
` command line interface. - Responses over 1MB will be buffered in a temporary file. Can be configured using the ``outbuf_overflow`` argument to :class:`WSGIHandler`. - **Breaking:** Removed support for Python 3.4. - **Breaking:** Removed ``aiohttp.concurrent`` helpers, which are no longer required with Python 3.5+. - **Breaking:** Removed ``configure_server()`` and ``close_server()`` helpers. Use :class:`WSGIHandler` directly. - **Breaking:** Removed ``serve()`` helpers. Use the :doc:`command line interface
` directly. 0.4.0 ----- - Requests over 512KB will be buffered in a temporary file. Can be configured using the ``inbuf_overflow`` argument to :class:`WSGIHandler`. - Minimum :ref:`aiohttp ` version is now 0.21.2. - **Breaking**: Maximum request body size is now 1GB. Can be configured using the ``max_request_body_size`` argument to :class:`WSGIHandler`. 0.3.0 ----- - ``PATH_INFO`` and ``SCRIPT_NAME`` now contain URL-quoted non-ascii characters, as per `PEP3333`_. - Minimum :ref:`aiohttp ` version is now 0.19.0. - **Breaking**: Removed support for Python3.3. 0.2.6 ----- - Excluded tests from distribution. 0.2.5 ----- - Updated to work with breaking changes in :ref:`aiohttp ` 0.17.0. 0.2.4 ----- - Workaround for error in :mod:`asyncio` debug mode on some Python versions when using a callable object, ``WSGIHandler.handle_request``. 0.2.3 ----- - Fixed bug with parsing ``SCRIPT_NAME``. 0.2.2 ----- - Implemented a standalone concurrent utility module for switching between the event loop and an executor. See ``aiohttp_wsgi.concurrent`` for more info. 0.2.1 ----- - Added ``on_finish`` parameter to ``serve()`` and ``configure_server()``. - Improved performance and predictability of processing streaming iterators from WSGI applications. 0.2.0 ----- - **BREAKING**: Removed ``WSGIMiddleware`` in favor of :class:`WSGIHandler` (required to support :ref:`aiohttp ` 0.15.0 without hacks). - Added support for :ref:`aiohttp ` 0.15.0. 0.1.2 ----- - Added ``socket`` argument to ``serve()`` and ``configure_server()``. - Added ``backlog`` argument to ``serve()`` and ``configure_server()``. 0.1.1 ----- - Fixed ``RuntimeError`` in :ref:`aiohttp ` (@jnurmine). - Added ``routes`` argument to ``serve()`` and ``configure_server()``. - Added ``static`` argument to ``serve()`` and ``configure_server()``. 0.1.0 ----- - First experimental release. - Buffering WSGI web server with threaded workers. - Public ``configure_server()`` and ``serve()`` API. .. include:: /_include/links.rst aiohttp-wsgi_0.10.0.orig/docs/conf.py0000644000000000000000000002235014240147630014410 0ustar00#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # aophttp_wsgi documentation build configuration file, created by # sphinx-quickstart on Thu Jun 2 08:41:36 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import aiohttp_wsgi # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"] intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "aiohttp": ("http://aiohttp.readthedocs.io/en/stable", None), } # Add any paths that contain templates here, relative to this directory. templates_path = [] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'aiohttp_wsgi' copyright = '2015, Dave Hall' author = 'Dave Hall' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = aiohttp_wsgi.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', '_include', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False suppress_warnings = ["image.nonlocal_uri"] # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = 'aophttp_wsgi v0.4.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'aophttp_wsgidoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'aophttp_wsgi.tex', 'aophttp_wsgi Documentation', 'Dave Hall', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'aophttp_wsgi', 'aophttp_wsgi Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'aophttp_wsgi', 'aophttp_wsgi Documentation', author, 'aophttp_wsgi', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False aiohttp-wsgi_0.10.0.orig/docs/contributing.rst0000644000000000000000000000077513533255766016377 0ustar00Contributing ============ Bug reports, bug fixes, and new features are always welcome. Please raise issues on `GitHub`_, and submit pull requests for any new code. Testing ------- It's recommended to test :mod:`aiohttp_wsgi` in a virtual environment using :mod:`venv`. Run the test suite using ``unittest``: .. code:: bash python -m unittest discover tests Contributors ------------ :mod:`aiohttp_wsgi` was developed by `Dave Hall`_ and other `contributors`_. .. include:: /_include/links.rst aiohttp-wsgi_0.10.0.orig/docs/index.rst0000644000000000000000000000003513533255766014764 0ustar00.. automodule:: aiohttp_wsgi aiohttp-wsgi_0.10.0.orig/docs/installation.rst0000644000000000000000000000102513533255766016356 0ustar00Installation ============ Requirements ------------ :mod:`aiohttp_wsgi` supports Python 3.5 and above. Installing ---------- It's recommended to install :mod:`aiohttp_wsgi` in a virtual environment using :mod:`venv`. Install :mod:`aiohttp_wsgi` using `pip`_. .. code:: bash pip install aiohttp_wsgi Upgrading --------- Upgrade :mod:`aiohttp_wsgi` using `pip`_: .. code:: bash pip install --upgrade aiohttp_wsgi .. important:: Check the :doc:`changelog` before upgrading. .. include:: /_include/links.rst aiohttp-wsgi_0.10.0.orig/docs/main.rst0000644000000000000000000000004613533255766014603 0ustar00.. automodule:: aiohttp_wsgi.__main__ aiohttp-wsgi_0.10.0.orig/docs/wsgi.rst0000644000000000000000000000004213533255766014624 0ustar00.. automodule:: aiohttp_wsgi.wsgi aiohttp-wsgi_0.10.0.orig/docs/_include/links.rst0000644000000000000000000000077214240147630016551 0ustar00.. _contributors: https://github.com/etianen/aiohttp-wsgi/graphs/contributors .. _documentation: https://aiohttp-wsgi.readthedocs.io/ .. _Dave Hall: http://etianen.com/ .. _Django: https://www.djangoproject.com/ .. _Flask: http://flask.pocoo.org/ .. _GitHub: http://github.com/etianen/aiohttp-wsgi .. _issue tracking: https://github.com/etianen/aiohttp-wsgi/issues .. _PEP3333: https://www.python.org/dev/peps/pep-3333/ .. _pip: https://pip.pypa.io .. _source code: https://github.com/etianen/aiohttp-wsgi aiohttp-wsgi_0.10.0.orig/tests/__init__.py0000644000000000000000000000000013533255766015436 0ustar00aiohttp-wsgi_0.10.0.orig/tests/base.py0000644000000000000000000000664014240147630014613 0ustar00from asyncio.base_events import Server import unittest from collections import namedtuple from contextlib import contextmanager from tempfile import NamedTemporaryFile from typing import Any, AsyncGenerator, ContextManager, Generator, Iterable import aiohttp import asyncio from aiohttp_wsgi.wsgi import run_server, WSGIEnviron, WSGIStartResponse from aiohttp_wsgi.utils import parse_sockname Response = namedtuple("Response", ("status", "reason", "headers", "content")) class TestClient: def __init__( self, test_case: unittest.TestCase, loop: asyncio.AbstractEventLoop, host: str, port: str, session: aiohttp.ClientSession, ) -> None: self._test_case = test_case self._loop = loop self._host = host self._port = port self._session = session def request(self, method: str = "GET", path: str = "/", **kwargs: Any) -> Response: uri = f"http://{self._host}:{self._port}{path}" response = self._loop.run_until_complete(self._session.request(method, uri, **kwargs)) return Response( response.status, response.reason, response.headers, self._loop.run_until_complete(response.read()), ) def assert_response(self, *args: Any, data: bytes = b"", **kwargs: Any) -> None: response = self.request(*args, data=data, **kwargs) self._test_case.assertEqual(response.status, 200) def noop_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: start_response("200 OK", [ ("Content-Type", "text/plain"), ]) return [] def echo_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: start_response("200 OK", [ ("Content-Type", "text/plain"), ]) return [environ["wsgi.input"].read()] async def streaming_request_body() -> AsyncGenerator: for _ in range(100): yield b"foobar" class AsyncTestCase(unittest.TestCase): @contextmanager def _run_server(self, *args: Any, **kwargs: Any) -> Generator[TestClient, None, None]: with run_server(*args, **kwargs) as (loop, site): assert site._server is not None assert isinstance(site._server, Server) assert site._server.sockets is not None host, port = parse_sockname(site._server.sockets[0].getsockname()) async def create_session() -> aiohttp.ClientSession: if host == "unix": connector: aiohttp.BaseConnector = aiohttp.UnixConnector(path=port) else: connector = aiohttp.TCPConnector() return aiohttp.ClientSession(connector=connector) session = loop.run_until_complete(create_session()) try: yield TestClient(self, loop, host, port, session) finally: loop.run_until_complete(session.close()) def run_server(self, *args: Any, **kwargs: Any) -> ContextManager[TestClient]: return self._run_server( *args, host="127.0.0.1", port="0", **kwargs, ) def run_server_unix(self, *args: Any, **kwargs: Any) -> ContextManager[TestClient]: socket_file = NamedTemporaryFile() socket_file.close() return self._run_server( *args, unix_socket=socket_file.name, **kwargs ) aiohttp-wsgi_0.10.0.orig/tests/static/0000755000000000000000000000000013533255766014626 5ustar00aiohttp-wsgi_0.10.0.orig/tests/test_environ.py0000644000000000000000000001167014240147630016417 0ustar00from concurrent.futures import ThreadPoolExecutor from functools import wraps from io import TextIOBase from typing import Callable, Iterable from tests.base import AsyncTestCase, noop_application from aiohttp_wsgi.wsgi import WSGIEnviron, WSGIStartResponse, WSGIApplication def environ_application(func: Callable[[WSGIEnviron], None]) -> WSGIApplication: @wraps(func) def do_environ_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: func(environ) return noop_application(environ, start_response) return do_environ_application @environ_application def assert_environ(environ: WSGIEnviron) -> None: assert environ["REQUEST_METHOD"] == "GET" assert environ["SCRIPT_NAME"] == "" assert environ["PATH_INFO"] == "/" assert environ["CONTENT_TYPE"] == "application/octet-stream" assert environ["CONTENT_LENGTH"] == "0" assert environ["SERVER_NAME"] == "127.0.0.1" assert int(environ["SERVER_PORT"]) > 0 assert environ["REMOTE_ADDR"] == "127.0.0.1" assert environ["REMOTE_HOST"] == "127.0.0.1" assert int(environ["REMOTE_PORT"]) > 0 assert environ["SERVER_PROTOCOL"] == "HTTP/1.1" assert environ["HTTP_FOO"] == "bar" assert environ["wsgi.version"] == (1, 0) assert environ["wsgi.url_scheme"] == "http" assert isinstance(environ["wsgi.errors"], TextIOBase) assert environ["wsgi.multithread"] assert not environ["wsgi.multiprocess"] assert not environ["wsgi.run_once"] assert isinstance(environ["asyncio.executor"], ThreadPoolExecutor) assert "aiohttp.request" in environ @environ_application def assert_environ_post(environ: WSGIEnviron) -> None: assert environ["REQUEST_METHOD"] == "POST" assert environ["CONTENT_TYPE"] == "text/plain" assert environ["CONTENT_LENGTH"] == "6" assert environ["wsgi.input"].read() == b"foobar" @environ_application def assert_environ_url_scheme(environ: WSGIEnviron) -> None: assert environ["wsgi.url_scheme"] == "https" @environ_application def assert_environ_unix_socket(environ: WSGIEnviron) -> None: assert environ["SERVER_NAME"] == "unix" assert environ["SERVER_PORT"].startswith("/") assert environ["REMOTE_HOST"] == "unix" assert environ["REMOTE_PORT"] == "" @environ_application def assert_environ_subdir(environ: WSGIEnviron) -> None: assert environ["SCRIPT_NAME"] == "" assert environ["PATH_INFO"] == "/foo" @environ_application def assert_environ_root_subdir(environ: WSGIEnviron) -> None: assert environ["SCRIPT_NAME"] == "/foo" assert environ["PATH_INFO"] == "" @environ_application def assert_environ_root_subdir_slash(environ: WSGIEnviron) -> None: assert environ["SCRIPT_NAME"] == "/foo" assert environ["PATH_INFO"] == "/" @environ_application def assert_environ_root_subdir_trailing(environ: WSGIEnviron) -> None: assert environ["SCRIPT_NAME"] == "/foo" assert environ["PATH_INFO"] == "/bar" @environ_application def assert_environ_quoted_path_info(environ: WSGIEnviron) -> None: assert environ['PATH_INFO'] == "/테/스/트" assert environ['RAW_URI'] == "/%ED%85%8C%2F%EC%8A%A4%2F%ED%8A%B8" assert environ['REQUEST_URI'] == "/%ED%85%8C%2F%EC%8A%A4%2F%ED%8A%B8" class EnvironTest(AsyncTestCase): def testEnviron(self) -> None: with self.run_server(assert_environ) as client: client.assert_response(headers={ "Content-Type": "application/octet-stream", "Foo": "bar", }) def testEnvironPost(self) -> None: with self.run_server(assert_environ_post) as client: client.assert_response( method="POST", headers={"Content-Type": "text/plain"}, data=b"foobar", ) def testEnvironUrlScheme(self) -> None: with self.run_server(assert_environ_url_scheme, url_scheme="https") as client: client.assert_response() def testEnvironUnixSocket(self) -> None: with self.run_server_unix(assert_environ_unix_socket) as client: client.assert_response() def testEnvironSubdir(self) -> None: with self.run_server(assert_environ_subdir) as client: client.assert_response(path="/foo") def testEnvironRootSubdir(self) -> None: with self.run_server(assert_environ_root_subdir, script_name="/foo") as client: client.assert_response(path="/foo") def testEnvironRootSubdirSlash(self) -> None: with self.run_server(assert_environ_root_subdir_slash, script_name="/foo") as client: client.assert_response(path="/foo/") def testEnvironRootSubdirTrailing(self) -> None: with self.run_server(assert_environ_root_subdir_trailing, script_name="/foo") as client: client.assert_response(path="/foo/bar") def testQuotedPathInfo(self) -> None: with self.run_server(assert_environ_quoted_path_info) as client: client.assert_response(path="/%ED%85%8C%2F%EC%8A%A4%2F%ED%8A%B8") aiohttp-wsgi_0.10.0.orig/tests/test_errors.py0000644000000000000000000000134214240147630016246 0ustar00import sys from typing import Iterable from tests.base import AsyncTestCase from aiohttp_wsgi.wsgi import WSGIEnviron, WSGIStartResponse def error_handling_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: try: start_response("200 OK", []) raise Exception("Boom!") except Exception: start_response("509 Boom", [], sys.exc_info()) # type: ignore return [b"Boom!"] class ErrorsTest(AsyncTestCase): def testErrorHandling(self) -> None: with self.run_server(error_handling_application) as client: response = client.request() self.assertEqual(response.status, 509) self.assertEqual(response.content, b"Boom!") aiohttp-wsgi_0.10.0.orig/tests/test_inbuf_overflow.py0000644000000000000000000000114314240147630017757 0ustar00from tests.base import AsyncTestCase, streaming_request_body, echo_application class InbufOverflowTest(AsyncTestCase): def testInbufOverflow(self) -> None: with self.run_server(echo_application, inbuf_overflow=3) as client: response = client.request(data="foobar") self.assertEqual(response.content, b"foobar") def testInbufOverflowStreaming(self) -> None: with self.run_server(echo_application, inbuf_overflow=20) as client: response = client.request(data=streaming_request_body()) self.assertEqual(response.content, b"foobar" * 100) aiohttp-wsgi_0.10.0.orig/tests/test_max_request_body_size.py0000644000000000000000000000115414240147630021337 0ustar00from tests.base import AsyncTestCase, streaming_request_body, noop_application class MaxRequestBodySizeTest(AsyncTestCase): def testMaxRequestBodySize(self) -> None: with self.run_server(noop_application, max_request_body_size=3) as client: response = client.request(data="foobar") self.assertEqual(response.status, 413) def testMaxRequestBodySizeStreaming(self) -> None: with self.run_server(noop_application, max_request_body_size=20) as client: response = client.request(data=streaming_request_body()) self.assertEqual(response.status, 413) aiohttp-wsgi_0.10.0.orig/tests/test_start_response.py0000644000000000000000000000333214240147630020006 0ustar00from typing import Iterable from tests.base import AsyncTestCase from aiohttp_wsgi.wsgi import WSGIEnviron, WSGIStartResponse CHUNK = b"foobar" * 1024 CHUNK_COUNT = 64 RESPONSE_CONTENT = CHUNK * CHUNK_COUNT def start_response_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: start_response("201 Created", [ ("Foo", "Bar"), ("Foo", "Baz"), ]) return [b"foobar"] def streaming_response_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: start_response("200 OK", []) for _ in range(CHUNK_COUNT): yield CHUNK def streaming_response_write_application(environ: WSGIEnviron, start_response: WSGIStartResponse) -> Iterable[bytes]: write = start_response("200 OK", []) for _ in range(CHUNK_COUNT): write(CHUNK) return [] class StartResponseTest(AsyncTestCase): def testStartResponse(self) -> None: with self.run_server(start_response_application) as client: response = client.request() self.assertEqual(response.status, 201) self.assertEqual(response.reason, "Created") self.assertEqual(response.headers.getall("Foo"), ["Bar", "Baz"]) self.assertEqual(response.content, b"foobar") def testStreamingResponse(self) -> None: with self.run_server(streaming_response_application) as client: response = client.request() self.assertEqual(response.content, RESPONSE_CONTENT) def testStreamingResponseWrite(self) -> None: with self.run_server(streaming_response_write_application) as client: response = client.request() self.assertEqual(response.content, RESPONSE_CONTENT) aiohttp-wsgi_0.10.0.orig/tests/test_static.py0000644000000000000000000000247714240147630016233 0ustar00import os from tests.base import AsyncTestCase, noop_application STATIC = (("/static", os.path.join(os.path.dirname(__file__), "static")),) class StaticTest(AsyncTestCase): def testStaticMiss(self) -> None: with self.run_server(noop_application, static=STATIC) as client: response = client.request() self.assertEqual(response.status, 200) self.assertEqual(response.content, b"") def testStaticHit(self) -> None: with self.run_server(noop_application, static=STATIC) as client: response = client.request(path="/static/text.txt") self.assertEqual(response.status, 200) self.assertEqual(response.content, b"Test file") def testStaticHitMissing(self) -> None: with self.run_server(noop_application, static=STATIC) as client: response = client.request(path="/static/missing.txt") self.assertEqual(response.status, 404) def testStaticHitCors(self) -> None: with self.run_server(noop_application, static=STATIC, static_cors="*") as client: response = client.request(path="/static/text.txt") self.assertEqual(response.status, 200) self.assertEqual(response.content, b"Test file") self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*") aiohttp-wsgi_0.10.0.orig/tests/test_wsgi.py0000644000000000000000000000050214240147630015700 0ustar00from wsgiref.validate import validator from tests.base import AsyncTestCase, noop_application validator_application = validator(noop_application) class EnvironTest(AsyncTestCase): def testValidWsgi(self) -> None: with self.run_server(validator_application) as client: client.assert_response() aiohttp-wsgi_0.10.0.orig/tests/static/text.txt0000644000000000000000000000001113533255766016343 0ustar00Test file