[
  {
    "path": ".github/ISSUE_TEMPLATE/1-issue.yml",
    "content": "name: Issue\ndescription: Report a bug or unexpected behavior. 🙏\n\nbody:\n  - type: markdown\n    attributes:\n      value:  Thank you for contributing to Uvicorn! ✊\n\n  - type: checkboxes\n    id: checks\n    attributes:\n      label: Initial Checks\n      description: Just making sure you open a discussion first. 🙏\n      options:\n        - label: I confirm this was discussed, and the maintainers suggest I open an issue.\n          required: true\n        - label: I'm aware that if I created this issue without a discussion, it may be closed without a response.\n          required: true\n\n  - type: textarea\n    id: discussion\n    attributes:\n      label: Discussion Link\n      description: |\n        Please link to the discussion that led to this issue.\n\n        If you haven't discussed this issue yet, please do so before opening an issue.\n      render: Text\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      description: |\n        Please explain what you're seeing and what you would expect to see.\n\n        Please provide as much detail as possible to make understanding and solving your problem as quick as possible. 🙏\n    validations:\n      required: true\n\n  - type: textarea\n    id: example\n    attributes:\n      label: Example Code\n      description: >\n        If applicable, please add a self-contained,\n        [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example)\n        demonstrating the bug.\n      render: Python\n\n  - type: textarea\n    id: version\n    attributes:\n      label: Python, Uvicorn & OS Version\n      description: |\n        Which version of Python & Uvicorn are you using, and which Operating System?\n\n        Please run the following command and copy the output below:\n\n        ```bash\n        python -m uvicorn --version\n        ```\n\n      render: Text\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser\nblank_issues_enabled: false\ncontact_links:\n  - name: Discussions\n    url: https://github.com/Kludex/uvicorn/discussions\n    about: The \"Discussions\" forum is where you want to start. 💖\n  - name: Chat\n    url: https://discord.com/invite/SWU73HffbV\n    about: Our community Discord server. 💬\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thanks for contributing to Uvicorn! 💚\nGiven this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->\n\n# Summary\n\n<!-- Write a small summary about what is happening here. -->\n\n# Checklist\n\n- [ ] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)\n- [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.\n- [ ] I've updated the documentation accordingly.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"uv\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    groups:\n      python-packages:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: monthly\n    groups:\n      github-actions:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/benchmark.yml",
    "content": "---\nname: CodSpeed\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  id-token: write\n  contents: read\n\njobs:\n  benchmarks:\n    name: Run benchmarks\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\" # v6.0.2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: \"3.13\"\n\n      - name: Install dependencies\n        run: scripts/install\n        shell: bash\n\n      - name: Run the benchmarks\n        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4\n        with:\n          mode: instrumentation\n          run: uv run pytest tests/benchmarks/ --codspeed -n 0\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "---\nname: Test Suite\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\njobs:\n  tests:\n    name: \"Python ${{ matrix.python-version }} ${{ matrix.os }}\"\n    runs-on: \"${{ matrix.os }}\"\n    timeout-minutes: 10\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\", \"3.14t\"]\n        os: [windows-latest, ubuntu-latest, macos-latest]\n\n    steps:\n      - uses: \"actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\" # v6.0.2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: ${{ matrix.python-version }}\n          enable-cache: ${{ matrix.os != 'windows-latest' }}\n\n      - name: Install dependencies\n        run: scripts/install\n        shell: bash\n\n      - name: Run linting checks\n        run: scripts/check\n        if: \"${{ matrix.os == 'ubuntu-latest'}}\"\n\n      - name: \"Build package & docs\"\n        run: scripts/build\n        shell: bash\n\n      - name: \"Run tests\"\n        run: scripts/test\n        shell: bash\n\n      - name: \"Enforce coverage\"\n        run: scripts/coverage\n        shell: bash\n\n  # https://github.com/marketplace/actions/alls-green#why\n  check:\n    if: always()\n    needs: [tests]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Decide whether the needed jobs succeeded or failed\n        uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2\n        with:\n          jobs: ${{ toJSON(needs) }}\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: \"3.11\"\n          enable-cache: true\n\n      - name: Install dependencies\n        run: scripts/install\n\n      - name: Build package & docs\n        run: scripts/build\n\n      - name: Upload package distributions\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: package-distributions\n          path: dist/\n\n      - name: Upload documentation\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: documentation\n          path: site/\n\n  pypi-publish:\n    runs-on: ubuntu-latest\n    needs: build\n    if: success() && startsWith(github.ref, 'refs/tags/')\n\n    permissions:\n      id-token: write\n\n    environment:\n      name: pypi\n      url: https://pypi.org/project/uvicorn\n\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: package-distributions\n          path: dist/\n\n      - name: Publish distribution 📦 to PyPI\n        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0\n\n  docs-publish:\n    runs-on: ubuntu-latest\n    needs: build\n\n    permissions:\n      contents: write\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: documentation\n          path: site/\n\n      - name: Configure Git Credentials\n        run: |\n          git config user.name github-actions[bot]\n          git config user.email 41898282+github-actions[bot]@users.noreply.github.com\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: \"3.12\"\n          enable-cache: true\n\n      - name: Install dependencies\n        run: scripts/install\n\n      - name: Publish documentation 📚 to GitHub Pages\n        run: uv run mkdocs gh-deploy --force\n\n  docs-cloudflare:\n    runs-on: ubuntu-latest\n    needs: build\n\n    environment:\n      name: cloudflare\n      url: https://uvicorn.dev\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: documentation\n          path: site/\n\n      - uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1\n        with:\n          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          command: >\n            pages deploy ./site\n            --project-name uvicorn\n            --commit-hash ${{ github.sha }}\n            --branch main\n"
  },
  {
    "path": ".gitignore",
    "content": ".cache\n.coverage\n.coverage.*\n.mypy_cache/\n__pycache__/\nuvicorn.egg-info/\nvenv/\nhtmlcov/\nsite/\ndist/\n.codspeed/\n"
  },
  {
    "path": "CITATION.cff",
    "content": "# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\ncff-version: 1.2.0\ntitle: Uvicorn\nmessage: >-\n  If you use this software, please cite it using the\n  metadata from this file.\ntype: software\nauthors:\n  - given-names: Marcelo\n    family-names: Trylesinski\n    email: marcelotryle@gmail.com\n  - given-names: Tom\n    family-names: Christie\n    email: tom@tomchristie.com\nrepository-code: \"https://github.com/Kludex/uvicorn\"\nurl: \"https://uvicorn.dev/\"\nabstract: Uvicorn is an ASGI web server implementation for Python.\nkeywords:\n  - asgi\n  - server\nlicense: BSD-3-Clause\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/).\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img width=\"320\" height=\"320\" src=\"https://raw.githubusercontent.com/tomchristie/uvicorn/main/docs/uvicorn.png\" alt='uvicorn'>\n</p>\n\n<p align=\"center\">\n<em>An ASGI web server, for Python.</em>\n</p>\n\n---\n\n[![Build Status](https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/uvicorn/actions)\n[![Package version](https://badge.fury.io/py/uvicorn.svg)](https://pypi.python.org/pypi/uvicorn)\n[![Supported Python Version](https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058)](https://pypi.org/project/uvicorn)\n[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs)\n\n---\n\n**Documentation**: [https://uvicorn.dev](https://uvicorn.dev)\n\n**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn)\n\n---\n\nUvicorn is an ASGI web server implementation for Python.\n\nUntil recently Python has lacked a minimal low-level server/application interface for\nasync frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to\nstart building a common set of tooling usable across all async frameworks.\n\nUvicorn supports HTTP/1.1 and WebSockets.\n\n## Quickstart\n\nInstall using `pip`:\n\n```shell\n$ pip install uvicorn\n```\n\nThis will install uvicorn with minimal (pure Python) dependencies.\n\n```shell\n$ pip install 'uvicorn[standard]'\n```\n\nThis will install uvicorn with \"Cython-based\" dependencies (where possible) and other \"optional extras\".\n\nIn this context, \"Cython-based\" means the following:\n\n- the event loop `uvloop` will be installed and used if possible.\n- the http protocol will be handled by `httptools` if possible.\n\nMoreover, \"optional extras\" means that:\n\n- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible.\n- the `--reload` flag in development mode will use `watchfiles`.\n- windows users will have `colorama` installed for the colored logs.\n- `python-dotenv` will be installed should you want to use the `--env-file` option.\n- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired.\n\nCreate an application, in `example.py`:\n\n```python\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n\n    await send({\n        'type': 'http.response.start',\n        'status': 200,\n        'headers': [\n            (b'content-type', b'text/plain'),\n        ],\n    })\n    await send({\n        'type': 'http.response.body',\n        'body': b'Hello, world!',\n    })\n```\n\nRun the server:\n\n```shell\n$ uvicorn example:app\n```\n\n---\n\n## Why ASGI?\n\nMost well established Python Web frameworks started out as WSGI-based frameworks.\n\nWSGI applications are a single, synchronous callable that takes a request and returns a response.\nThis doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections,\nwhich WSGI doesn't support well.\n\nHaving an async concurrency model also allows for options such as lightweight background tasks,\nand can be less of a limiting factor for endpoints that have long periods being blocked on network\nI/O such as dealing with slow HTTP requests.\n\n---\n\n## Alternative ASGI servers\n\nA strength of the ASGI protocol is that it decouples the server implementation\nfrom the application framework. This allows for an ecosystem of interoperating\nwebservers and application frameworks.\n\n### Daphne\n\nThe first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne].\n\nIt is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets.\n\nAny of the example applications given here can equally well be run using `daphne` instead.\n\n```\n$ pip install daphne\n$ daphne app:App\n```\n\n### Hypercorn\n\n[Hypercorn][hypercorn] was initially part of the Quart web framework, before\nbeing separated out into a standalone ASGI server.\n\nHypercorn supports HTTP/1.1, HTTP/2, and WebSockets.\n\nIt also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`.\n\n```\n$ pip install hypercorn\n$ hypercorn app:App\n```\n\n### Mangum\n\n[Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway.\n\n### Granian\n\n[Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets.\n\n---\n\n<p align=\"center\"><i>Uvicorn is <a href=\"https://github.com/Kludex/uvicorn/blob/main/LICENSE.md\">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>&mdash; 🦄  &mdash;</p>\n\n[asgi]: https://asgi.readthedocs.io/en/latest/\n[daphne]: https://github.com/django/daphne\n[hypercorn]: https://github.com/pgjones/hypercorn\n[trio]: https://trio.readthedocs.io\n[mangum]: https://github.com/jordaneremieff/mangum\n[granian]: https://github.com/emmett-framework/granian\n"
  },
  {
    "path": "docs/CNAME",
    "content": "www.uvicorn.org\n"
  },
  {
    "path": "docs/concepts/asgi.md",
    "content": "## ASGI\n\n**Uvicorn** uses the [ASGI specification](https://asgi.readthedocs.io/en/latest/) for interacting with an application.\n\nThe application should expose an async callable which takes three arguments:\n\n* `scope` - A dictionary containing information about the incoming connection.\n* `receive` - A channel on which to receive incoming messages from the server.\n* `send` - A channel on which to send outgoing messages to the server.\n\nTwo common patterns you might use are either function-based applications:\n\n```python\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    ...\n```\n\nOr instance-based applications:\n\n```python\nclass App:\n    async def __call__(self, scope, receive, send):\n        assert scope['type'] == 'http'\n        ...\n\napp = App()\n```\n\nIt's good practice for applications to raise an exception on scope types\nthat they do not handle.\n\nThe content of the `scope` argument, and the messages expected by `receive` and `send` depend on the protocol being used.\n\nThe format for HTTP messages is described in the [ASGI HTTP Message format](https://asgi.readthedocs.io/en/latest/specs/www.html).\n\n### HTTP Scope\n\nAn incoming HTTP request might have a connection `scope` like this:\n\n```python\n{\n    'type': 'http',\n    'scheme': 'http',\n    'root_path': '',\n    'server': ('127.0.0.1', 8000),\n    'http_version': '1.1',\n    'method': 'GET',\n    'path': '/',\n    'headers': [\n        (b'host', b'127.0.0.1:8000'),\n        (b'user-agent', b'curl/7.51.0'),\n        (b'accept', b'*/*')\n    ]\n}\n```\n\n### HTTP Messages\n\nThe instance coroutine communicates back to the server by sending messages to the `send` coroutine.\n\n```python\nawait send({\n    'type': 'http.response.start',\n    'status': 200,\n    'headers': [\n        [b'content-type', b'text/plain'],\n    ]\n})\nawait send({\n    'type': 'http.response.body',\n    'body': b'Hello, world!',\n})\n```\n\n### Requests & responses\n\nHere's an example that displays the method and path used in the incoming request:\n\n```python\nasync def app(scope, receive, send):\n    \"\"\"\n    Echo the method and path back in an HTTP response.\n    \"\"\"\n    assert scope['type'] == 'http'\n\n    body = f'Received {scope[\"method\"]} request to {scope[\"path\"]}'\n    await send({\n        'type': 'http.response.start',\n        'status': 200,\n        'headers': [\n            [b'content-type', b'text/plain'],\n        ]\n    })\n    await send({\n        'type': 'http.response.body',\n        'body': body.encode('utf-8'),\n    })\n```\n\n### Reading the request body\n\nYou can stream the request body without blocking the asyncio task pool,\nby fetching messages from the `receive` coroutine.\n\n```python\nasync def read_body(receive):\n    \"\"\"\n    Read and return the entire body from an incoming ASGI message.\n    \"\"\"\n    body = b''\n    more_body = True\n\n    while more_body:\n        message = await receive()\n        body += message.get('body', b'')\n        more_body = message.get('more_body', False)\n\n    return body\n\n\nasync def app(scope, receive, send):\n    \"\"\"\n    Echo the request body back in an HTTP response.\n    \"\"\"\n    body = await read_body(receive)\n    await send({\n        'type': 'http.response.start',\n        'status': 200,\n        'headers': [\n            (b'content-type', b'text/plain'),\n            (b'content-length', str(len(body)).encode())\n        ]\n    })\n    await send({\n        'type': 'http.response.body',\n        'body': body,\n    })\n```\n\n### Streaming responses\n\nYou can stream responses by sending multiple `http.response.body` messages to\nthe `send` coroutine.\n\n```python\nimport asyncio\n\n\nasync def app(scope, receive, send):\n    \"\"\"\n    Send a slowly streaming HTTP response back to the client.\n    \"\"\"\n    await send({\n        'type': 'http.response.start',\n        'status': 200,\n        'headers': [\n            [b'content-type', b'text/plain'],\n        ]\n    })\n    for chunk in [b'Hello', b', ', b'world!']:\n        await send({\n            'type': 'http.response.body',\n            'body': chunk,\n            'more_body': True\n        })\n        await asyncio.sleep(1)\n    await send({\n        'type': 'http.response.body',\n        'body': b'',\n    })\n```\n\n---\n\n## Why ASGI?\n\nMost well established Python Web frameworks started out as WSGI-based frameworks.\n\nWSGI applications are a single, synchronous callable that takes a request and returns a response.\nThis doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections,\nwhich WSGI doesn't support well.\n\nHaving an async concurrency model also allows for options such as lightweight background tasks,\nand can be less of a limiting factor for endpoints that have long periods being blocked on network\nI/O such as dealing with slow HTTP requests.\n\n---\n\n## Alternative ASGI servers\n\nA strength of the ASGI protocol is that it decouples the server implementation\nfrom the application framework. This allows for an ecosystem of interoperating\nwebservers and application frameworks.\n\n### Daphne\n\nThe first ASGI server implementation, originally developed to power Django Channels, is\n[the Daphne webserver](https://github.com/django/daphne).\n\nIt is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets.\n\nAny of the example applications given here can equally well be run using `daphne` instead.\n\n```shell\npip install daphne\ndaphne app:App\n```\n\n### Hypercorn\n\n[Hypercorn](https://github.com/pgjones/hypercorn) was initially part of the Quart web framework,\nbefore being separated out into a standalone ASGI server.\n\nHypercorn supports HTTP/1.1, HTTP/2, HTTP/3 and WebSockets.\n\n```shell\npip install hypercorn\nhypercorn app:App\n```\n\n---\n\n## ASGI frameworks\n\nYou can use Uvicorn, Daphne, or Hypercorn to run any ASGI framework.\n\nFor small services you can also write ASGI applications directly.\n\n### Starlette\n\n[Starlette](https://github.com/Kludex/starlette) is a lightweight ASGI framework/toolkit.\n\nIt is ideal for building high performance asyncio services, and supports both HTTP and WebSockets.\n\n### Django Channels\n\nThe ASGI specification was originally designed for use with [Django Channels](https://channels.readthedocs.io/en/latest/).\n\nChannels is a little different to other ASGI frameworks in that it provides\nan asynchronous frontend onto a threaded-framework backend. It allows Django\nto support WebSockets, background tasks, and long-running connections,\nwith application code still running in a standard threaded context.\n\n### Quart\n\n[Quart](https://pgjones.gitlab.io/quart/) is a Flask-like ASGI web framework.\n\n### FastAPI\n\n[**FastAPI**](https://github.com/tiangolo/fastapi) is an API framework based on **Starlette** and **Pydantic**, heavily inspired by previous server versions of **APIStar**.\n\nYou write your API function parameters with Python 3.6+ type declarations and get automatic data conversion, data validation, OpenAPI schemas (with JSON Schemas) and interactive API documentation UIs.\n\n### BlackSheep\n\n[BlackSheep](https://www.neoteroi.dev/blacksheep/) is a web framework based on ASGI, inspired by Flask and ASP.NET Core.\n\nIts most distinctive features are built-in support for dependency injection, automatic binding of parameters by request handler's type annotations, and automatic generation of OpenAPI documentation and Swagger UI.\n\n### Falcon\n\n[Falcon](https://falconframework.org) is a minimalist REST and app backend framework for Python, with a focus on reliability, correctness, and performance at scale.\n\n### Muffin\n\n[Muffin](https://github.com/klen/muffin) is a fast, lightweight and asynchronous ASGI web-framework for Python 3.\n\n### Litestar\n\n[Litestar](https://litestar.dev) is a powerful, lightweight and flexible ASGI framework.\n\nIt includes everything that's needed to build modern APIs - from data serialization and validation to websockets, ORM integration, session management, authentication and more.\n\n### Panther\n\n[Panther](https://PantherPy.github.io/) is a fast & friendly web framework for building async APIs with Python 3.10+.\n\nIt has built-in Document-oriented Database, Caching System, Authentication and Permission Classes, Visual API Monitoring and also supports Websocket, Throttling, Middlewares.\n"
  },
  {
    "path": "docs/concepts/event-loop.md",
    "content": "# Event Loop\n\nUvicorn provides two event loop implementations that you can choose from using the [`--loop`](../settings.md#implementation) option:\n\n```bash\nuvicorn main:app --loop <auto|asyncio|uvloop>\n```\n\nBy default, Uvicorn uses `--loop auto`, which automatically selects:\n\n1. **uvloop** - If [uvloop](https://github.com/MagicStack/uvloop) is installed, Uvicorn will use it for maximum performance\n2. **asyncio** - If uvloop is not available, Uvicorn falls back to Python's built-in asyncio event loop\n\nSince `uvloop` is not compatible with Windows or PyPy, it is not available on these platforms.\n\nOn Windows, the asyncio implementation uses the standard [`ProactorEventLoop`][asyncio.ProactorEventLoop] in single-process mode.\nWhen running with `--reload` or multiple workers, it uses [`SelectorEventLoop`][asyncio.SelectorEventLoop] instead.\n\n??? info \"Why can `ProactorEventLoop` fail with multiple processes on Windows?\"\n    If you want to know more about it, you can read the issue [#cpython/122240](https://github.com/python/cpython/issues/122240).\n\n## Custom Event Loop\n\nYou can use custom event loop implementations by specifying a module path and function name using the colon notation:\n\n```bash\nuvicorn main:app --loop <module>:<function>\n```\n\nThe function should return a callable that creates a new event loop instance.\n\n### rloop\n\n[rloop](https://github.com/gi0baro/rloop) is an experimental AsyncIO event loop implemented in Rust on top of the [mio](https://github.com/tokio-rs/mio) crate. It aims to provide high performance through Rust's systems programming capabilities.\n\nYou can install it with:\n\n=== \"pip\"\n    ```bash\n    pip install rloop\n    ```\n=== \"uv\"\n    ```bash\n    uv add rloop\n    ```\n\nYou can run `uvicorn` with `rloop` with the following command:\n\n```bash\nuvicorn main:app --loop rloop:new_event_loop\n```\n\n!!! warning \"Experimental\"\n    rloop is currently **experimental** and **not suited for production usage**. It is only available on **Unix systems**.\n\n### Winloop\n\n[Winloop](https://github.com/Vizonex/Winloop) is an alternative library that brings uvloop-like performance to Windows. Since uvloop is based on libuv and doesn't support Windows, Winloop provides a Windows-compatible implementation with significant performance improvements over the standard Windows event loop policies.\n\nYou can install it with:\n\n=== \"pip\"\n    ```bash\n    pip install winloop\n    ```\n=== \"uv\"\n    ```bash\n    uv add winloop\n    ```\n\nYou can run `uvicorn` with `Winloop` with the following command:\n\n```bash\nuvicorn main:app --loop winloop:new_event_loop\n```\n"
  },
  {
    "path": "docs/concepts/lifespan.md",
    "content": "Since Uvicorn is an ASGI server, it supports the\n[ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html).\nThis allows you to run **startup** and **shutdown** events for your application.\n\nThe lifespan protocol is useful for initializing resources that need to be available throughout\nthe lifetime of the application, such as database connections, caches, or other services.\n\nKeep in mind that the lifespan is executed **only once per application instance**. If you have\nmultiple workers, each worker will execute the lifespan independently.\n\n## Lifespan Architecture\n\nThe lifespan protocol runs as a sibling task alongside your main application, allowing both to execute concurrently.\n\nLet's see how Uvicorn handles the lifespan and main application tasks:\n\n```mermaid\nsequenceDiagram\n    participant Server as Uvicorn Server\n    participant LifespanTask as Lifespan Task\n    participant AppTask as Application Task\n    participant UserApp as User Application\n\n    Note over Server: ✅ Server starts\n\n    Server->>+LifespanTask: spawn_task(lifespan_handler)\n\n    LifespanTask->>UserApp: {\"type\": \"lifespan.startup\"}\n\n    Note over UserApp: Initialize databases, caches, etc.\n\n    UserApp-->>LifespanTask: {\"type\": \"lifespan.startup.complete\"}\n    LifespanTask->>Server: ✅ Startup complete\n\n    Server->>+AppTask: spawn_task(application_handler)\n    Note over AppTask: ✅ Ready for requests\n\n    rect rgb(240, 248, 255)\n        Note over LifespanTask, AppTask: Both tasks running concurrently\n\n        par Lifespan maintains state\n            LifespanTask->>LifespanTask: Keep lifespan connection alive\n        and Application serves requests\n            AppTask->>UserApp: HTTP/WebSocket requests\n            UserApp-->>AppTask: Responses\n        end\n    end\n\n    Note over Server: Shutdown signal received\n\n    Server->>AppTask: Stop accepting new connections\n    AppTask->>AppTask: Complete pending requests\n\n    LifespanTask->>UserApp: {\"type\": \"lifespan.shutdown\"}\n\n    Note over UserApp: Cleanup databases, caches, etc.\n\n    UserApp-->>LifespanTask: {\"type\": \"lifespan.shutdown.complete\"}\n\n    LifespanTask->>-Server: Lifespan task complete\n    AppTask->>-Server: Application task complete\n\n    Note over Server: ✅ Server stopped\n```\n\nHaving the lifespan task run as a sibling task is a deliberate design choice. It could have been implemented as a parent task that spawns the\napplication task. This decision has the implication that if you create a [`ContextVar`][contextvars.ContextVar]\nin the lifespan task, it will not be available in the application task.\n\n## Usage\n\nLet's see an example of a minimal (but complete) ASGI application that implements the lifespan protocol:\n\n```python title=\"ASGI application with lifespan\" hl_lines=\"3-11\"\nasync def app(scope, receive, send):\n    if scope['type'] == 'lifespan':\n        while True:\n            message = await receive()\n            if message['type'] == 'lifespan.startup':\n                print(\"Application is starting up...\")\n                await send({'type': 'lifespan.startup.complete'})\n            elif message['type'] == 'lifespan.shutdown':\n                print(\"Application is shutting down...\")\n                await send({'type': 'lifespan.shutdown.complete'})\n                return\n    elif scope['type'] == 'http':\n        await send({\n            'type': 'http.response.start',\n            'status': 200,\n            'headers': [(b'content-type', b'text/plain')],\n        })\n        await send({'type': 'http.response.body', 'body': b'Hello, World!'})\n    else:\n        raise RuntimeError(\"This server doesn't support WebSocket.\")\n```\n\nYou can run the above application with `uvicorn main:app`. Then you'll see the print statements when the\napplication starts. You can also try to send some HTTP requests to it, and it will respond with \"Hello, World!\".\nAnd if you stop the server (`CTRL + C`), it will print `\"Application is shutting down...\"`.\n\n## Disabling Lifespan\n\nIf you want to disable the lifespan protocol, you can do so by setting the `lifespan` option to `off` when running Uvicorn:\n\n```bash\nuvicorn main:app --lifespan off\n```\n\nBy default, Uvicorn will automatically enable the lifespan protocol if the application supports it.\n"
  },
  {
    "path": "docs/concepts/websockets.md",
    "content": "**Uvicorn** supports the WebSocket protocol as defined in [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455).\n\n## Upgrade Process\n\nThe WebSocket protocol starts as an HTTP connection that gets \"upgraded\" to a WebSocket connection\nthrough a handshake process. Here's how it works:\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    participant ASGI App\n\n    Note over Client,ASGI App: WebSocket Handshake Process\n\n    Client->>Server: HTTP GET Request\n    Note right of Client: Headers:<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Key: [key]<br/>Sec-WebSocket-Version: 13\n\n    Server->>ASGI App: websocket.connect event\n    Note right of Server: Scope type: \"websocket\"\n\n    alt Connection Accepted\n        ASGI App->>Server: {\"type\": \"websocket.accept\"}\n        Server->>Client: HTTP 101 Switching Protocols\n        Note right of Server: Headers:<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Accept: [hash]\n\n        Note over Client,ASGI App: WebSocket Connection Established\n\n        loop Message Exchange\n            Client->>Server: WebSocket Frame\n            Server->>ASGI App: websocket.receive event\n            ASGI App->>Server: {\"type\": \"websocket.send\", \"text\": \"...\"}\n            Server->>Client: WebSocket Frame\n        end\n\n        alt Client Closes\n            Client->>Server: Close Frame\n            Server->>ASGI App: websocket.disconnect event\n        else Server Closes\n            ASGI App->>Server: {\"type\": \"websocket.close\"}\n            Server->>Client: Close Frame\n        end\n\n    else Connection Rejected\n        ASGI App->>Server: {\"type\": \"websocket.http.response.start\", \"status\": 403}\n        Server->>Client: HTTP 403 Forbidden\n    end\n```\n\n1. **Initial HTTP Request**: The client sends a regular HTTP GET request with special headers indicating it wants to upgrade to WebSocket:\n    - `Upgrade: websocket`\n    - `Connection: Upgrade`\n    - `Sec-WebSocket-Key`: A base64-encoded random key\n    - `Sec-WebSocket-Version: 13`\n\n2. **Server Processing**: Uvicorn receives the request and creates a WebSocket scope, sending a `websocket.connect` event to the ASGI application.\n\n3. **Application Decision**: The ASGI app decides whether to accept or reject the connection based on authentication, authorization, or other logic.\n\n4. **Handshake Completion**: If accepted, the server responds with HTTP 101 status and the computed `Sec-WebSocket-Accept` header.\n\n5. **Full-Duplex Communication**: Once upgraded, both client and server can send messages at any time using WebSocket frames.\n\n6. **Connection Termination**: Either side can initiate closing the connection with a close frame.\n\n## ASGI WebSocket Events\n\n**Uvicorn** translates WebSocket protocol messages into ASGI events:\n\n- `websocket.connect`: Sent when a client requests a WebSocket upgrade\n- `websocket.receive`: Sent when a message is received from the client\n- `websocket.disconnect`: Sent when the connection is closed\n\nThe ASGI app can respond with:\n\n- `websocket.accept`: Accept the connection upgrade with an optional subprotocol\n- `websocket.send`: Send a message to the client\n- `websocket.close`: Close the connection with an optional status code\n\nYou can read more about it on the [ASGI documentation](https://asgi.readthedocs.io/en/latest/specs/www.html#websocket).\n\n## Protocol Implementations\n\n**Uvicorn** has three implementations of the WebSocket protocol.\n\n### WSProto Protocol\n\nThis implementation was the first implemented. It uses the\n[`wsproto`](https://python-hyper.org/projects/wsproto/en/stable/) package underneath.\n\nYou can choose this protocol by setting the `--ws` option to `wsproto`.\n\n### WebSocket Protocol\n\nThis implementation uses the [`websockets`](https://websockets.readthedocs.io/) package as dependency.\n\nBy default, if you have `websockets` installed, Uvicorn will use this protocol.\n\n### WebSockets SansIO Protocol\n\nSince `websockets` deprecated the API Uvicorn uses to run the previous protocol, we had to create this new\nprotocol that uses the `websockets` SansIO API.\n\nYou can choose this protocol by setting the `--ws` option to `websockets-sansio`.\n\n!!! note\n    The SansIO implementation was released in Uvicorn version 0.35.0 in June 2025.\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\nThank you for being interested in contributing to Uvicorn.\nThere are many ways you can contribute to the project:\n\n- Using Uvicorn on your stack and [reporting bugs/issues you find](https://github.com/Kludex/uvicorn/issues/new)\n- [Implementing new features and fixing bugs](https://github.com/Kludex/uvicorn/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)\n- [Review Pull Requests of others](https://github.com/Kludex/uvicorn/pulls)\n- Write documentation\n- Participate in discussions\n\n## Reporting Bugs, Issues or Feature Requests\n\nFound something that Uvicorn should support?\nStumbled upon some unexpected behaviour?\nNeed a missing functionality?\n\nContributions should generally start out from a previous discussion.\nYou can reach out someone at the [community chat](https://discord.com/invite/SWU73HffbV)\nor at the [github discussions tab](https://github.com/Kludex/uvicorn/discussions).\n\nWhen creating a new topic in the discussions tab, possible bugs may be raised\nas a \"Potential Issue\" discussion, feature requests may be raised as an\n\"Ideas\" discussion. We can then determine if the discussion needs\nto be escalated into an \"Issue\" or not, or if we'd consider a pull request.\n\nTry to be more descriptive as you can and in case of a bug report,\nprovide as much information as possible like:\n\n- OS platform\n- Python version\n- Installed dependencies and versions (`python -m pip freeze`)\n- Code snippet\n- Error traceback\n\nYou should always try to reduce any examples to the *simplest possible case*\nthat demonstrates the issue.\n\nSome possibly useful tips for narrowing down potential issues...\n\n- Does the issue exist with a specific supervisor like `Multiprocess` or more than one?\n- Does the issue exist on asgi, or wsgi, or both?\n- Are you running Uvicorn in conjunction with Gunicorn, others, or standalone?\n\n## Development\n\nTo start developing Uvicorn create a **fork** of the\n[Uvicorn repository](https://github.com/Kludex/uvicorn) on GitHub.\n\nThen clone your fork with the following command replacing `YOUR-USERNAME` with\nyour GitHub username:\n\n```shell\n$ git clone https://github.com/YOUR-USERNAME/uvicorn\n```\n\nYou can now install the project and its dependencies using:\n\n```shell\n$ cd uvicorn\n$ scripts/install\n```\n\n## Testing and Linting\n\nWe use custom shell scripts to automate testing, linting,\nand documentation building workflow.\n\nTo run the tests, use:\n\n```shell\n$ scripts/test\n```\n\nAny additional arguments will be passed to `pytest`. See the [pytest documentation](https://docs.pytest.org/en/latest/how-to/usage.html) for more information.\n\nFor example, to run a single test script:\n\n```shell\n$ scripts/test tests/test_cli.py\n```\n\nTo run the code auto-formatting:\n\n```shell\n$ scripts/lint\n```\n\nLastly, to run code checks separately (they are also run as part of `scripts/test`), run:\n\n```shell\n$ scripts/check\n```\n\n## Documenting\n\nDocumentation pages are located under the `docs/` folder.\n\nTo run the documentation site locally (useful for previewing changes), use:\n\n```shell\n$ scripts/docs serve\n```\n\n## Resolving Build / CI Failures\n\nOnce you've submitted your pull request, the test suite will\nautomatically run, and the results will show up in GitHub.\nIf the test suite fails, you'll want to click through to the\n\"Details\" link, and try to identify why the test suite failed.\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/Kludex/uvicorn/main/docs/img/gh-actions-fail.png\" alt='Failing PR commit status'>\n</p>\n\nHere are some common ways the test suite can fail:\n\n### Check Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/Kludex/uvicorn/main/docs/img/gh-actions-fail-check.png\" alt='Failing GitHub action lint job'>\n</p>\n\nThis job failing means there is either a code formatting issue or type-annotation issue.\nYou can look at the job output to figure out why it's failed or within a shell run:\n\n```shell\n$ scripts/check\n```\n\nIt may be worth it to run `$ scripts/lint` to attempt auto-formatting the code\nand if that job succeeds commit the changes.\n\n### Docs Job Failed\n\nThis job failing means the documentation failed to build. This can happen for\na variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`.\n\n### Python 3.X Job Failed\n\nThis job failing means the unit tests failed or not all code paths are covered by unit tests.\n\nIf tests are failing you will see this message under the coverage report:\n\n`=== 1 failed, 354 passed, 1 skipped, 1 xfailed in 37.08s ===`\n\nIf tests succeed but coverage doesn't reach 100%, you will see this\nmessage under the coverage report:\n\n`Coverage failure: total of 98 is less than fail-under=100`\n\n## Releasing\n\n*This section is targeted at Uvicorn maintainers.*\n\nBefore releasing a new version, create a pull request that includes:\n\n- **An update to the changelog**:\n    - We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/).\n    - [Compare](https://github.com/Kludex/uvicorn/compare/) `main` with the tag of the latest release, and list all entries that are of interest to our users:\n        - Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes.\n        - Things that **should not** go in the changelog: changes to documentation, tests or tooling.\n        - Try sorting entries in descending order of impact / importance.\n        - Keep it concise and to-the-point. 🎯\n- **A version bump**: see `__init__.py`.\n\nFor an example, see [#1006](https://github.com/Kludex/uvicorn/pull/1107).\n\nOnce the release PR is merged, create a\n[new release](https://github.com/Kludex/uvicorn/releases/new) including:\n\n- Tag version like `0.13.3`.\n- Release title `Version 0.13.3`\n- Description copied from the changelog.\n\nOnce created this release will be automatically uploaded to PyPI.\n"
  },
  {
    "path": "docs/deployment/docker.md",
    "content": "# Dockerfile\n\n**Docker** is a popular choice for modern application deployment. However, creating a good Dockerfile from scratch can be challenging. This guide provides a **solid foundation** that works well for most Python projects.\n\nWhile the example below won't fit every use case, it offers an excellent starting point that you can adapt to your specific needs.\n\n\n## Quickstart\n\nFor this example, we'll need to install [`docker`](https://docs.docker.com/get-docker/),\n[docker-compose](https://docs.docker.com/compose/install/) and\n[`uv`](https://docs.astral.sh/uv/getting-started/installation/).\n\nThen, let's create a new project with `uv`:\n\n```bash\nuv init app\n```\n\nThis will create a new project with a basic structure:\n\n```bash\napp/\n├── main.py\n├── pyproject.toml\n└── README.md\n```\n\nOn `main.py`, let's create a simple ASGI application:\n\n```python title=\"main.py\"\nasync def app(scope, receive, send):\n    body = \"Hello, world!\"\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [\n                [b\"content-type\", b\"text/plain\"],\n                [b\"content-length\", len(body)],\n            ],\n        }\n    )\n    await send(\n        {\n            \"type\": \"http.response.body\",\n            \"body\": body.encode(\"utf-8\"),\n        }\n    )\n```\n\nWe need to include `uvicorn` in the dependencies:\n\n```bash\nuv add uvicorn\n```\n\nThis will also create a `uv.lock` file. :sunglasses:\n\n??? tip \"What is `uv.lock`?\"\n\n    `uv.lock` is a `uv` specific lockfile. A lockfile is a file that contains the exact versions of the dependencies\n    that were installed when the `uv.lock` file was created.\n\n    This allows for deterministic builds and consistent deployments.\n\nJust to make sure everything is working, let's run the application:\n\n```bash\nuv run uvicorn main:app\n```\n\nYou should see the following output:\n\n```bash\nINFO:     Started server process [62727]\nINFO:     Waiting for application startup.\nINFO:     ASGI 'lifespan' protocol appears unsupported.\nINFO:     Application startup complete.\nINFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n```\n\n## Dockerfile\n\nWe'll create a **cache-aware Dockerfile** that optimizes build times. The key strategy is to install dependencies first, then copy the project files. This approach leverages Docker's caching mechanism to significantly speed up rebuilds.\n\n```dockerfile title=\"Dockerfile\"\nFROM python:3.12-slim\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\n\n# Change the working directory to the `app` directory\nWORKDIR /app\n\n# Install dependencies\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    --mount=type=bind,source=uv.lock,target=uv.lock \\\n    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\\n    uv sync --frozen --no-install-project\n\n# Copy the project into the image\nADD . /app\n\n# Sync the project\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    uv sync --frozen\n\n# Run with uvicorn\nCMD [\"uv\", \"run\", \"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n```\n\nA common question is **\"how many workers should I run?\"**. The image above uses a single Uvicorn worker.\nThe recommended approach is to let your orchestration system manage the number of deployed containers rather than\nrelying on the process manager inside the container.\n\nYou can read more about this in the\n[Decouple applications](https://docs.docker.com/build/building/best-practices/#decouple-applications) section\nof the Docker documentation.\n\n!!! warning \"For production, create a non-root user!\"\n    When running in production, you should create a non-root user and run the container as that user.\n\nTo make sure it works, let's build the image and run it:\n\n```bash\ndocker build -t my-app .\ndocker run -p 8000:8000 my-app\n```\n\nFor more information on using uv with Docker, refer to the\n[official uv Docker integration guide](https://docs.astral.sh/uv/guides/integration/docker/).\n\n## Docker Compose\n\nWhen running in development, it's often useful to have a way to hot-reload the application when code changes.\n\nLet's create a `docker-compose.yml` file to run the application:\n\n```yaml title=\"docker-compose.yml\"\nservices:\n  backend:\n    build: .\n    ports:\n      - \"8000:8000\"\n    environment:\n      - UVICORN_RELOAD=true\n    volumes:\n      - .:/app\n    tty: true\n```\n\nYou can run the application with `docker compose up` and it will automatically rebuild the image when code changes.\n\nNow you have a fully working development environment! :tada:\n"
  },
  {
    "path": "docs/deployment/index.md",
    "content": "Server deployment is a complex area, that will depend on what kind of service you're deploying Uvicorn onto.\n\nAs a general rule, you probably want to:\n\n* Run `uvicorn --reload` from the command line for local development.\n* Run `gunicorn -k uvicorn.workers.UvicornWorker` for production.\n* Additionally run behind Nginx for self-hosted deployments.\n* Finally, run everything behind a CDN for caching support, and serious DDOS protection.\n\n## Running from the command line\n\nTypically you'll run `uvicorn` from the command line.\n\n```bash\n$ uvicorn main:app --reload --port 5000\n```\n\nThe ASGI application should be specified in the form `path.to.module:instance.path`.\n\nWhen running locally, use `--reload` to turn on auto-reloading.\n\nThe `--reload` and `--workers` arguments are **mutually exclusive**.\n\nTo see the complete set of available options, use `uvicorn --help`:\n\n```bash\n{{ uvicorn_help }}\n```\n\nSee the [settings documentation](../settings.md) for more details on the supported options for running uvicorn.\n\n## Running programmatically\n\nTo run directly from within a Python program, you should use `uvicorn.run(app, **config)`. For example:\n\n```py title=\"main.py\"\nimport uvicorn\n\nclass App:\n    ...\n\napp = App()\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"main:app\", host=\"127.0.0.1\", port=5000, log_level=\"info\")\n```\n\nThe set of configuration options is the same as for the command line tool.\n\nNote that the application instance itself *can* be passed instead of the app\nimport string.\n\n```python\nuvicorn.run(app, host=\"127.0.0.1\", port=5000, log_level=\"info\")\n```\n\nHowever, this style only works if you are not using multiprocessing (`workers=NUM`)\nor reloading (`reload=True`), so we recommend using the import string style.\n\nAlso note that in this case, you should put `uvicorn.run` into `if __name__ == '__main__'` clause in the main module.\n\n!!! note\n    The `reload` and `workers` parameters are **mutually exclusive**.\n\n## Using a process manager\n\nRunning Uvicorn using a process manager ensures that you can run multiple processes in a resilient manner, and allows you to perform server upgrades without dropping requests.\n\nA process manager will handle the socket setup, start-up multiple server processes, monitor process aliveness, and listen for signals to provide for processes restarts, shutdowns, or dialing up and down the number of running processes.\n\n### Built-in\n\nUvicorn includes a `--workers` option that allows you to run multiple worker processes.\n\n```bash\n$ uvicorn main:app --workers 4\n```\n\nUnlike gunicorn, uvicorn does not use pre-fork, but uses [`spawn`](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods), which allows uvicorn's multiprocess manager to still work well on Windows.\n\nThe default process manager monitors the status of child processes and automatically restarts child processes that die unexpectedly. Not only that, it will also monitor the status of the child process through the pipeline. When the child process is accidentally stuck, the corresponding child process will be killed through an unstoppable system signal or interface.\n\nYou can also manage child processes by sending specific signals to the main process. (Not supported on Windows.)\n\n- `SIGHUP`: Work processeses are graceful restarted one after another. If you update the code, the new worker process will use the new code.\n- `SIGTTIN`: Increase the number of worker processes by one.\n- `SIGTTOU`: Decrease the number of worker processes by one.\n\n### Gunicorn\n\n!!! warning\n    The `uvicorn.workers` module is deprecated and will be removed in a future release.\n\n    You should use the [`uvicorn-worker`](https://github.com/Kludex/uvicorn-worker) package instead.\n\n    ```bash\n    python -m pip install uvicorn-worker\n    ```\n\nGunicorn is probably the simplest way to run and manage Uvicorn in a production setting. Uvicorn includes a gunicorn worker class that means you can get set up with very little configuration.\n\nThe following will start Gunicorn with four worker processes:\n\n`gunicorn -w 4 -k uvicorn.workers.UvicornWorker`\n\nThe `UvicornWorker` implementation uses the `uvloop` and `httptools` implementations. To run under PyPy you'll want to use pure-python implementation instead. You can do this by using the `UvicornH11Worker` class.\n\n`gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker`\n\nGunicorn provides a different set of configuration options to Uvicorn, so  some options such as `--limit-concurrency` are not yet supported when running with Gunicorn.\n\nIf you need to pass uvicorn's config arguments to gunicorn workers then you'll have to subclass `UvicornWorker`:\n\n```python\nfrom uvicorn.workers import UvicornWorker\n\nclass MyUvicornWorker(UvicornWorker):\n    CONFIG_KWARGS = {\"loop\": \"asyncio\", \"http\": \"h11\", \"lifespan\": \"off\"}\n```\n\n### Supervisor\n\nTo use `supervisor` as a process manager you should either:\n\n* Hand over the socket to uvicorn using its file descriptor, which supervisor always makes available as `0`, and which must be set in the `fcgi-program` section.\n* Or use a UNIX domain socket for each `uvicorn` process.\n\nA simple supervisor configuration might look something like this:\n\n```ini title=\"supervisord.conf\"\n[supervisord]\n\n[fcgi-program:uvicorn]\nsocket=tcp://localhost:8000\ncommand=venv/bin/uvicorn --fd 0 main:App\nnumprocs=4\nprocess_name=uvicorn-%(process_num)d\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n```\n\nThen run with `supervisord -n`.\n\n## Running behind Nginx\n\nUsing Nginx as a proxy in front of your Uvicorn processes may not be necessary, but is recommended for additional resilience. Nginx can deal with serving your static media and buffering slow requests, leaving your application servers free from load as much as possible.\n\nIn managed environments such as `Heroku`, you won't typically need to configure Nginx, as your server processes will already be running behind load balancing proxies.\n\nThe recommended configuration for proxying from Nginx is to use a UNIX domain socket between Nginx and whatever the process manager that is being used to run Uvicorn. If using Uvicorn directly you can bind it to a UNIX domain socket using `uvicorn --uds /path/to/socket.sock <...>`.\n\nWhen running your application behind one or more proxies you will want to make sure that each proxy sets appropriate headers to ensure that your application can properly determine the client address of the incoming connection, and if the connection was over `http` or `https`. For more information see [Proxies and Forwarded Headers](#proxies-and-forwarded-headers) below.\n\nHere's how a simple Nginx configuration might look. This example includes setting proxy headers, and using a UNIX domain socket to communicate with the application server.\n\nIt also includes some basic configuration to forward websocket connections.\nFor more info on this, check [Nginx recommendations](https://nginx.org/en/docs/http/websocket.html).\n\n```conf\nhttp {\n  server {\n    listen 80;\n    client_max_body_size 4G;\n\n    server_name example.com;\n\n    location / {\n      proxy_set_header Host $http_host;\n      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n      proxy_set_header X-Forwarded-Proto $scheme;\n      proxy_set_header Upgrade $http_upgrade;\n      proxy_set_header Connection $connection_upgrade;\n      proxy_redirect off;\n      proxy_buffering off;\n      proxy_pass http://uvicorn;\n    }\n\n    location /static {\n      # path for static files\n      root /path/to/app/static;\n    }\n  }\n\n  map $http_upgrade $connection_upgrade {\n    default upgrade;\n    '' close;\n  }\n\n  upstream uvicorn {\n    server unix:/tmp/uvicorn.sock;\n  }\n\n}\n```\n\nUvicorn's `--proxy-headers` behavior may not be sufficient for more complex proxy configurations that use different combinations of headers, or where the application is running behind more than one intermediary proxying service.\n\nIn those cases, you might want to use an ASGI middleware to set the `client` and `scheme` dependant on the request headers.\n\n## Running behind a CDN\n\nRunning behind a content delivery network, such as Cloudflare or Cloud Front, provides a serious layer of protection against DDoS attacks. Your service will be running behind huge clusters of proxies and load balancers that are designed for handling huge amounts of traffic, and have capabilities for detecting and closing off connections from DDoS attacks.\n\nProper usage of cache control headers can mean that a CDN is able to serve large amounts of data without always having to forward the request on to your server.\n\nContent Delivery Networks can also be a low-effort way to provide HTTPS termination.\n\n## Running with HTTPS\n\nTo run uvicorn with https, a certificate and a private key are required.\nThe recommended way to get them is using [Let's Encrypt](https://letsencrypt.org/).\n\nFor local development with https, it's possible to use [mkcert](https://github.com/FiloSottile/mkcert)\nto generate a valid certificate and private key.\n\n```bash\n$ uvicorn main:app --port 5000 --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem\n```\n\n### Running gunicorn worker\n\nIt's also possible to use certificates with uvicorn's worker for gunicorn.\n\n```bash\n$ gunicorn --keyfile=./key.pem --certfile=./cert.pem -k uvicorn.workers.UvicornWorker main:app\n```\n\n## Proxies and Forwarded Headers\n\nWhen running an application behind one or more proxies, certain information about the request is lost.\nTo avoid this most proxies will add headers containing this information for downstream servers to read.\n\nUvicorn currently supports the following headers:\n\n- `X-Forwarded-For` ([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For))\n- `X-Forwarded-Proto`([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto))\n\nUvicorn can use these headers to correctly set the client and protocol in the request.\nHowever as anyone can set these headers you must configure which \"clients\" you will trust to have set them correctly.\n\nUvicorn can be configured to trust IP Addresses (e.g. `127.0.0.1`), IP Networks (e.g. `10.100.0.0/16`),\nor Literals (e.g. `/path/to/socket.sock`). When running from CLI these are configured using `--forwarded-allow-ips`.\n\n!!! Warning \"Only trust clients you can actually trust!\"\n    Incorrectly trusting other clients can lead to malicious actors spoofing their apparent client address to your application.\n\nFor more information, check [`ProxyHeadersMiddleware`](https://github.com/Kludex/uvicorn/blob/main/uvicorn/middleware/proxy_headers.py).\n\n### Client Port\n\nCurrently if the `ProxyHeadersMiddleware` is able to retrieve a trusted client value then the client's port will be set to `0`.\nThis is because port information is lost when using these headers.\n\n### UNIX Domain Sockets (UDS)\n\nAlthough it is common for UNIX Domain Sockets to be used for communicating between various HTTP servers, they can mess with some of the expected received values as they will be various non-address strings or missing values.\n\nFor example:\n\n- when NGINX itself is running behind a UDS it will add the literal `unix:` as the client in the `X-Forwarded-For` header.\n- When Uvicorn is running behind a UDS the initial client will be `None`.\n\n### Trust Everything\n\nRather than specifying what to trust, you can instruct Uvicorn to trust all clients using the literal `\"*\"`.\nYou should only set this when you know you can trust all values within the forwarded headers (e.g. because\nyour proxies remove the existing headers before setting their own).\n"
  },
  {
    "path": "docs/index.md",
    "content": "<style>\n  .md-typeset h1,\n  .md-content__button {\n    display: none;\n  }\n</style>\n\n<p align=\"center\">\n  <img width=\"320\" height=\"320\" src=\"../../uvicorn.png\" alt='uvicorn'>\n</p>\n\n<p align=\"center\">\n<em>An ASGI web server, for Python.</em>\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/Kludex/uvicorn/actions\">\n    <img src=\"https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg\" alt=\"Test Suite\">\n</a>\n<a href=\"https://pypi.org/project/uvicorn/\">\n    <img src=\"https://badge.fury.io/py/uvicorn.svg\" alt=\"Package version\">\n</a>\n<a href=\"https://pypi.org/project/uvicorn\" target=\"_blank\">\n    <img src=\"https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058\" alt=\"Supported Python versions\">\n</a>\n<a href=\"https://discord.gg/RxKUF5JuHs\">\n    <img src=\"https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2\" alt=\"Discord\">\n</a>\n</p>\n\n---\n\n**Documentation**: [https://uvicorn.dev](https://uvicorn.dev)\n\n**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn)\n\n---\n\n**Uvicorn** is an [ASGI](concepts/asgi.md) web server implementation for Python.\n\nUntil recently Python has lacked a minimal low-level server/application interface for\nasync frameworks. The [ASGI specification](https://asgi.readthedocs.io/en/latest/) fills this gap,\nand means we're now able to start building a common set of tooling usable across all async frameworks.\n\nUvicorn currently supports **HTTP/1.1** and **WebSockets**.\n\n## Quickstart\n\n**Uvicorn** is available on [PyPI](https://pypi.org/project/uvicorn/) so installation is as simple as:\n\n=== \"pip\"\n    ```bash\n    pip install uvicorn\n    ```\n\n=== \"uv\"\n    ```bash\n    uv add uvicorn\n    ```\n\nSee the [installation documentation](installation.md) for more information.\n\n---\n\nLet's create a simple ASGI application to run with Uvicorn:\n\n```python title=\"main.py\"\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n\n    await send({\n        'type': 'http.response.start',\n        'status': 200,\n        'headers': [\n            (b'content-type', b'text/plain'),\n            (b'content-length', b'13'),\n        ],\n    })\n    await send({\n        'type': 'http.response.body',\n        'body': b'Hello, world!',\n    })\n```\n\nThen we can run it with Uvicorn:\n\n```shell\nuvicorn main:app\n```\n\n---\n\n## Usage\n\nThe uvicorn command line tool is the easiest way to run your application.\n\n### Command line options\n\n```bash\n{{ uvicorn_help }}\n```\n\nFor more information, see the [settings documentation](settings.md).\n\n### Running programmatically\n\nThere are several ways to run uvicorn directly from your application.\n\n#### `uvicorn.run`\n\nIf you're looking for a programmatic equivalent of the `uvicorn` command line interface, use `uvicorn.run()`:\n\n```py title=\"main.py\"\nimport uvicorn\n\nasync def app(scope, receive, send):\n    ...\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"main:app\", port=5000, log_level=\"info\")\n```\n\n#### `Config` and `Server` instances\n\nFor more control over configuration and server lifecycle, use `uvicorn.Config` and `uvicorn.Server`:\n\n```py title=\"main.py\"\nimport uvicorn\n\nasync def app(scope, receive, send):\n    ...\n\nif __name__ == \"__main__\":\n    config = uvicorn.Config(\"main:app\", port=5000, log_level=\"info\")\n    server = uvicorn.Server(config)\n    server.run()\n```\n\nIf you'd like to run Uvicorn from an already running async environment, use `uvicorn.Server.serve()` instead:\n\n```py title=\"main.py\"\nimport asyncio\nimport uvicorn\n\nasync def app(scope, receive, send):\n    ...\n\nasync def main():\n    config = uvicorn.Config(\"main:app\", port=5000, log_level=\"info\")\n    server = uvicorn.Server(config)\n    await server.serve()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n### Running with Gunicorn\n\n!!! warning\n    The `uvicorn.workers` module is deprecated and will be removed in a future release.\n\n    You should use the [`uvicorn-worker`](https://github.com/Kludex/uvicorn-worker) package instead.\n\n    ```bash\n    python -m pip install uvicorn-worker\n    ```\n\n[Gunicorn](https://gunicorn.org/) is a mature, fully featured server and process manager.\n\nUvicorn includes a Gunicorn worker class allowing you to run ASGI applications,\nwith all of Uvicorn's performance benefits, while also giving you Gunicorn's\nfully-featured process management.\n\nThis allows you to increase or decrease the number of worker processes on the\nfly, restart worker processes gracefully, or perform server upgrades without downtime.\n\nFor production deployments we recommend using gunicorn with the uvicorn worker class.\n\n```\ngunicorn example:app -w 4 -k uvicorn.workers.UvicornWorker\n```\n\nFor a [PyPy](https://pypy.org/) compatible configuration use `uvicorn.workers.UvicornH11Worker`.\n\nFor more information, see the [deployment documentation](deployment/index.md).\n\n### Application factories\n\nThe `--factory` flag allows loading the application from a factory function, rather than an application instance directly. The factory will be called with no arguments and should return an ASGI application.\n\n```py title=\"main.py\"\ndef create_app():\n    app = ...\n    return app\n```\n\n```shell\nuvicorn --factory main:create_app\n```\n"
  },
  {
    "path": "docs/installation.md",
    "content": "**Uvicorn** is available on [PyPI](https://pypi.org/project/uvicorn/) so installation is as simple as:\n\n=== \"pip\"\n\n    ```bash\n    pip install uvicorn\n    ```\n\n=== \"uv\"\n\n    ```bash\n    uv add uvicorn\n    ```\n\nThe above will install Uvicorn with the minimal set of dependencies:\n\n- [`h11`](https://github.com/python-hyper/h11) — Pure Python sans-io HTTP/1.1 implementation.\n- [`click`](https://github.com/pallets/click) — Command line interface library.\n\nIf you are running on Python 3.10 or early versions,\n[`typing_extensions`](https://github.com/python/typing_extensions) will also be installed.\n\n## Optional Dependencies\n\nThere are many optional dependencies that can be installed to add support for various features.\n\nIf you just want to install all of them at once, you can use the `standard` extra:\n\n=== \"pip\"\n    ```bash\n    pip install 'uvicorn[standard]'\n    ```\n\n=== \"uv\"\n    ```bash\n    uv add 'uvicorn[standard]'\n    ```\n\nThe `standard` extra installs the following dependencies:\n\n- **[`uvloop`](https://github.com/MagicStack/uvloop) — Fast, drop-in replacement of the built-in asyncio event loop.**\n\n    When `uvloop` is installed, Uvicorn will use it by default.\n\n- **[`httptools`](https://github.com/MagicStack/httptools) — Python binding for the Node.js HTTP parser.**\n\n    When `httptools` is installed, Uvicorn will use it by default for HTTP/1.1 parsing.\n\n    You can read this issue to understand how it compares with `h11`: [h11/issues/9](https://github.com/python-hyper/h11/issues/9).\n\n- **[`websockets`](https://websockets.readthedocs.io/en/stable/) — WebSocket library for Python.**\n\n    When `websockets` is installed, Uvicorn will use it by default for WebSocket handling.\n\n    You can alternatively install **[`wsproto`](https://github.com/python-hyper/wsproto)** and set the `--ws`\n    option to `wsproto` to use it instead.\n\n- **[`watchfiles`](https://github.com/samuelcolvin/watchfiles) — Simple, modern and high performance file\n    watching and code reload in python.**\n\n    When `watchfiles` is installed, Uvicorn will use it by default for the `--reload` option.\n\n- **[`colorama`](https://github.com/tartley/colorama) — Cross-platform support for ANSI terminal\n    colors.**\n\n    This is installed only on Windows, to provide colored logs.\n\n- **[`python-dotenv`](https://github.com/theskumar/python-dotenv) — Reads key-value pairs from a `.env` file\n    and adds them to the environment.**\n\n    This is installed to allow you to use the `--env-file` option.\n\n- **[`PyYAML`](https://github.com/yaml/pyyaml) — YAML parser and emitter for Python.**\n\n    This is installed to allow you to provide a `.yaml` file to the `--log-config` option.\n"
  },
  {
    "path": "docs/overrides/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block extrahead %}\n  {{ super() }}\n  <script>\n    // Redirect starlette.io to starlette.dev\n    if (window.location.hostname === 'www.uvicorn.org' || window.location.hostname === 'uvicorn.org') {\n      const newUrl = window.location.href.replace(/^https?:\\/\\/(www\\.)?uvicorn\\.org/, 'https://uvicorn.dev');\n      window.location.replace(newUrl);\n    }\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "docs/overrides/partials/nav.html",
    "content": "{% import \"partials/nav-item.html\" as item with context %}\n\n<!-- Determine class according to configuration -->\n {% set class = \"md-nav md-nav--primary\" %}\n {% if \"navigation.tabs\" in features %}\n   {% set class = class ~ \" md-nav--lifted\" %}\n {% endif %}\n {% if \"toc.integrate\" in features %}\n   {% set class = class ~ \" md-nav--integrated\" %}\n {% endif %}\n\n <!-- Main navigation -->\n <nav\n   class=\"{{ class }}\"\n   aria-label=\"{{ lang.t('nav.title') }}\"\n   data-md-level=\"0\"\n >\n\n   <!-- Site title -->\n   <label class=\"md-nav__title\" for=\"__drawer\">\n     <a\n       href=\"{{ config.extra.homepage | d(nav.homepage.url, true) | url }}\"\n       title=\"{{ config.site_name | e }}\"\n       class=\"md-nav__button md-logo\"\n       aria-label=\"{{ config.site_name }}\"\n       data-md-component=\"logo\"\n     >\n       {% include \"partials/logo.html\" %}\n     </a>\n     {{ config.site_name }}\n   </label>\n\n   <!-- Repository information -->\n   {% if config.repo_url %}\n     <div class=\"md-nav__source\">\n       {% include \"partials/source.html\" %}\n     </div>\n   {% endif %}\n\n   <!-- Navigation list -->\n   <ul class=\"md-nav__list\" data-md-scrollfix>\n     {% for nav_item in nav %}\n       {% set path = \"__nav_\" ~ loop.index %}\n       {{ item.render(nav_item, path, 1) }}\n     {% endfor %}\n   </ul>\n </nav>\n"
  },
  {
    "path": "docs/overrides/partials/toc-item.html",
    "content": "<!-- Copied from https://github.com/squidfunk/mkdocs-material/issues/4827#issuecomment-1869812019 -->\n<li class=\"md-nav__item\"></li>\n<a href=\"{{ toc_item.url }}\" class=\"md-nav__link\">\n    <span class=\"md-ellipsis\">\n        {{ toc_item.title }}\n    </span>\n</a>\n\n<!-- Table of contents list -->\n{% if toc_item.children %}\n<nav class=\"md-nav\" aria-label=\"{{ toc_item.title | striptags }}\">\n    <ul class=\"md-nav__list\">\n        {% for toc_item in toc_item.children %}\n        {% if not page.meta.toc_depth or toc_item.level <= page.meta.toc_depth %} {% include \"partials/toc-item.html\" %}\n            {% endif %} {% endfor %} </ul>\n</nav>\n{% endif %}\n</li>\n"
  },
  {
    "path": "docs/plugins/main.py",
    "content": "from __future__ import annotations as _annotations\n\nimport re\nimport subprocess\nfrom functools import lru_cache\n\nfrom mkdocs.config import Config\nfrom mkdocs.structure.files import Files\nfrom mkdocs.structure.pages import Page\n\n\ndef on_page_content(html: str, page: Page, config: Config, files: Files) -> str:\n    \"\"\"Called on each page after the markdown is converted to HTML.\"\"\"\n    html = add_hyperlink_to_pull_request(html, page, config, files)\n    return html\n\n\ndef add_hyperlink_to_pull_request(html: str, page: Page, config: Config, files: Files) -> str:\n    \"\"\"Add hyperlink on PRs mentioned on the release notes page.\n\n    If we find \"(#\\\\d+)\" it will be added an hyperlink to https://github.com/Kludex/uvicorn/pull/$1.\n    \"\"\"\n    if not page.file.name == \"release-notes\":\n        return html\n\n    return re.sub(r\"\\(#(\\d+)\\)\", r\"(<a href='https://github.com/Kludex/uvicorn/pull/\\1'>#\\1</a>)\", html)\n\n\ndef on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str:\n    \"\"\"Called on each file after it is read and before it is converted to HTML.\"\"\"\n    markdown = uvicorn_print_help(markdown, page)\n    return markdown\n\n\ndef uvicorn_print_help(markdown: str, page: Page) -> str:\n    return re.sub(r\"{{ *uvicorn_help *}}\", get_uvicorn_help(), markdown)\n\n\n@lru_cache\ndef get_uvicorn_help():\n    output = subprocess.run([\"uvicorn\", \"--help\"], capture_output=True, check=True)\n    return output.stdout.decode()\n"
  },
  {
    "path": "docs/release-notes.md",
    "content": "---\ntoc_depth: 2\n---\n\n## 0.42.0 (March 16, 2026)\n\n### Changed\n\n* Use `bytearray` for request body accumulation to avoid O(n^2) allocation on fragmented bodies (#2845)\n\n### Fixed\n\n* Escape brackets and backslash in httptools `HEADER_RE` regex (#2824)\n* Fix multiple issues in websockets sans-io implementation (#2825)\n\n## 0.41.0 (February 16, 2026)\n\n### Added\n\n* Add `--limit-max-requests-jitter` to stagger worker restarts (#2707)\n* Add socket path to `scope[\"server\"]` (#2561)\n\n### Changed\n\n* Rename `LifespanOn.error_occured` to `error_occurred` (#2776)\n\n### Fixed\n\n* Ignore permission denied errors in watchfiles reloader (#2817)\n* Ensure lifespan shutdown runs when `should_exit` is set during startup (#2812)\n* Reduce the log level of 'request limit exceeded' messages (#2788)\n\n## 0.40.0 (December 21, 2025)\n\n### Remove\n\n* Drop support for Python 3.9 (#2772)\n\n## 0.39.0 (December 21, 2025)\n\n### Fixed\n\n* Send close frame on ASGI return for WebSockets (#2769)\n* Explicitly start ASGI run with empty context (#2742)\n\n## 0.38.0 (October 18, 2025)\n\n### Added\n\n* Support Python 3.14 (#2723)\n\n## 0.37.0 (September 23, 2025)\n\n### Added\n\n* Add `--timeout-worker-healthcheck` option (#2711)\n* Add `os.PathLike[str]` type to `ssl_ca_certs` (#2676)\n\n## 0.36.1 (September 23, 2025)\n\n### Fixed\n\n* Raise an exception when calling removed `Config.setup_event_loop()` (#2709)\n\n## 0.36.0 (September 20, 2025)\n\n### Added\n\n* Support custom IOLOOPs (#2435)\n* Allow to provide importable string in `--http`, `--ws` and `--loop` (#2658)\n\n## 0.35.0 (June 28, 2025)\n\n### Added\n\n* Add `WebSocketsSansIOProtocol` (#2540)\n\n### Changed\n\n* Refine help message for option `--proxy-headers` (#2653)\n\n## 0.34.3 (June 1, 2025)\n\n### Fixed\n\n* Don't include `cwd()` when non-empty `--reload-dirs` is passed (#2598)\n* Apply `get_client_addr` formatting to WebSocket logging (#2636)\n\n## 0.34.2 (April 19, 2025)\n\n### Fixed\n\n* Flush stdout buffer on Windows to trigger reload (#2604)\n\n## 0.34.1 (April 13, 2025)\n\n### Deprecated\n\n* Deprecate `ServerState` in the main module (#2581)\n\n## 0.34.0 (December 15, 2024)\n\n### Added\n\n* Add `content-length` to 500 response in `wsproto` implementation (#2542)\n\n### Removed\n\n* Drop support for Python 3.8 (#2543)\n\n## 0.33.0 (December 14, 2024)\n\n### Removed\n\n* Remove `WatchGod` support for `--reload` (#2536)\n\n## 0.32.1 (November 20, 2024)\n\n### Fixed\n\n* Drop ASGI spec version to 2.3 on HTTP scope (#2513)\n* Enable httptools lenient data on `httptools >= 0.6.3` (#2488)\n\n## 0.32.0 (October 15, 2024)\n\n### Added\n\n* Officially support Python 3.13 (#2482)\n* Warn when `max_request_limit` is exceeded (#2430)\n\n## 0.31.1 (October 9, 2024)\n\n### Fixed\n\n* Support WebSockets 0.13.1 (#2471)\n* Restore support for `[*]` in trusted hosts (#2480)\n* Add `PathLike[str]` type hint for `ssl_keyfile` (#2481)\n\n## 0.31.0 (September 27, 2024)\n\n### Added\n\nImprove `ProxyHeadersMiddleware` (#2468) and (#2231):\n\n- Fix the host for requests from clients running on the proxy server itself.\n- Fallback to host that was already set for empty x-forwarded-for headers.\n- Also allow to specify IP Networks as trusted hosts. This greatly simplifies deployments\n  on docker swarm/kubernetes, where the reverse proxy might have a dynamic IP.\n    - This includes support for IPv6 Address/Networks.\n\n## 0.30.6 (August 13, 2024)\n\n### Fixed\n\n- Don't warn when upgrade is not WebSocket and dependencies are installed (#2360)\n\n## 0.30.5 (August 2, 2024)\n\n### Fixed\n\n- Don't close connection before receiving body on H11 (#2408)\n\n## 0.30.4 (July 31, 2024)\n\n### Fixed\n\n- Close connection when `h11` sets client state to `MUST_CLOSE` (#2375)\n\n## 0.30.3 (July 20, 2024)\n\n### Fixed\n\n- Suppress `KeyboardInterrupt` from CLI and programmatic usage (#2384)\n- `ClientDisconnect` inherits from `OSError` instead of `IOError` (#2393)\n\n## 0.30.2 (July 20, 2024)\n\n### Added\n\n- Add `reason` support to [`websocket.disconnect`](https://asgi.readthedocs.io/en/latest/specs/www.html#disconnect-receive-event-ws) event (#2324)\n\n### Fixed\n\n- Iterate subprocesses in-place on the process manager (#2373)\n\n## 0.30.1 (June 2, 2024)\n\n### Fixed\n\n- Allow horizontal tabs `\\t` in response header values (#2345)\n\n## 0.30.0 (May 28, 2024)\n\n### Added\n\n- New multiprocess manager (#2183)\n- Allow `ConfigParser` or a `io.IO[Any]` on `log_config` (#1976)\n\n### Fixed\n\n- Suppress side-effects of signal propagation (#2317)\n- Send `content-length` header on 5xx (#2304)\n\n### Deprecated\n\n- Deprecate the `uvicorn.workers` module (#2302)\n\n## 0.29.0 (March 19, 2024)\n\n### Added\n\n- Cooperative signal handling (#1600)\n\n## 0.28.1 (March 19, 2024)\n\n### Fixed\n\n- Revert raise `ClientDisconnected` on HTTP (#2276)\n\n## 0.28.0 (March 9, 2024)\n\n### Added\n\n- Raise `ClientDisconnected` on `send()` when client disconnected (#2220)\n\n### Fixed\n\n- Except `AttributeError` on `sys.stdin.fileno()` for Windows IIS10 (#1947)\n- Use `X-Forwarded-Proto` for WebSockets scheme when the proxy provides it (#2258)\n\n## 0.27.1 (February 10, 2024)\n\n- Fix spurious LocalProtocolError errors when processing pipelined requests (#2243)\n\n## 0.27.0.post1 (January 29, 2024)\n\n### Fixed\n\n- Fix nav overrides for newer version of Mkdocs Material (#2233)\n\n## 0.27.0 (January 22, 2024)\n\n### Added\n\n- Raise `ClientDisconnect(IOError)` on `send()` when client disconnected (#2218)\n- Bump ASGI WebSocket spec version to 2.4 (#2221)\n\n## 0.26.0 (January 16, 2024)\n\n### Changed\n\n- Update `--root-path` to include the root path prefix in the full ASGI `path` as per the ASGI spec (#2213)\n- Use `__future__.annotations` on some internal modules (#2199)\n\n## 0.25.0 (December 17, 2023)\n\n### Added\n\n- Support the WebSocket Denial Response ASGI extension (#1916)\n\n### Fixed\n\n- Allow explicit hidden file paths on `--reload-include` (#2176)\n- Properly annotate `uvicorn.run()` (#2158)\n\n## 0.24.0.post1 (November 6, 2023)\n\n### Fixed\n\n- Revert mkdocs-material from 9.1.21 to 9.2.6 (#2148)\n\n## 0.24.0 (November 4, 2023)\n\n### Added\n\n- Support Python 3.12 (#2145)\n- Allow setting `app` via environment variable `UVICORN_APP` (#2106)\n\n## 0.23.2 (July 31, 2023)\n\n### Fixed\n\n- Maintain the same behavior of `websockets` from 10.4 on 11.0 (#2061)\n\n## 0.23.1 (July 18, 2023)\n\n### Fixed\n\n- Add `typing_extensions` for Python 3.10 and lower (#2053)\n\n## 0.23.0 (July 10, 2023)\n\n### Added\n\n- Add `--ws-max-queue` parameter WebSockets (#2033)\n\n### Removed\n\n- Drop support for Python 3.7 (#1996)\n- Remove `asgiref` as typing dependency (#1999)\n\n### Fixed\n\n- Set `scope[\"scheme\"]` to `ws` or `wss` instead of `http` or `https` on `ProxyHeadersMiddleware` for WebSockets (#2043)\n\n### Changed\n\n- Raise `ImportError` on circular import (#2040)\n- Use `logger.getEffectiveLevel()` instead of `logger.level` to check if log level is `TRACE` (#1966)\n\n## 0.22.0 (April 28, 2023)\n\n### Added\n\n- Add `--timeout-graceful-shutdown` parameter (#1950)\n- Handle `SIGBREAK` on Windows (#1909)\n\n### Fixed\n\n- Shutdown event is now being triggered on Windows when using hot reload (#1584)\n- `--reload-delay` is effectively used on the `watchfiles` reloader (#1930)\n\n## 0.21.1 (March 16, 2023)\n\n### Fixed\n\n- Reset lifespan state on each request (#1903)\n\n## 0.21.0 (March 9, 2023)\n\n### Added\n\n- Introduce lifespan state (#1818)\n- Allow headers to be sent as iterables on H11 implementation (#1782)\n- Improve discoverability when --port=0 is used (#1890)\n\n### Changed\n\n- Avoid importing `h11` and `pyyaml` when not needed to improve import time (#1846)\n- Replace current native `WSGIMiddleware` implementation by `a2wsgi` (#1825)\n- Change default `--app-dir` from \".\" (dot) to \"\" (empty string) (#1835)\n\n### Fixed\n\n- Send code 1012 on shutdown for WebSockets (#1816)\n- Use `surrogateescape` to encode headers on `websockets` implementation (#1005)\n- Fix warning message on reload failure (#1784)\n\n## 0.20.0 (November 20, 2022)\n\n### Added\n\n- Check if handshake is completed before sending frame on `wsproto` shutdown (#1737)\n- Add default headers to WebSockets implementations (#1606 & #1747)\n- Warn user when `reload` and `workers` flag are used together (#1731)\n\n### Fixed\n\n- Use correct `WebSocket` error codes on `close` (#1753)\n- Send disconnect event on connection lost for `wsproto` (#996)\n- Add `SIGQUIT` handler to `UvicornWorker` (#1710)\n- Fix crash on exist with \"--uds\" if socket doesn't exist (#1725)\n- Annotate `CONFIG_KWARGS` in `UvicornWorker` class (#1746)\n\n### Removed\n\n- Remove conditional on `RemoteProtocolError.event_hint` on `wsproto` (#1486)\n- Remove unused `handle_no_connect` on `wsproto` implementation (#1759)\n\n## 0.19.0 (October 19, 2022)\n\n### Added\n\n- Support Python 3.11 (#1652)\n- Bump minimal `httptools` version to `0.5.0` (#1645)\n- Ignore HTTP/2 upgrade and optionally ignore WebSocket upgrade (#1661)\n- Add `py.typed` to comply with PEP 561 (#1687)\n\n### Fixed\n\n- Set `propagate` to `False` on \"uvicorn\" logger (#1288)\n- USR1 signal is now handled correctly on `UvicornWorker`. (#1565)\n- Use path with query string on `WebSockets` logs (#1385)\n- Fix behavior on which \"Date\" headers were not updated on the same connection (#1706)\n\n### Removed\n\n- Remove the `--debug` flag (#1640)\n- Remove the `DebugMiddleware` (#1697)\n\n## 0.18.3 (August 24, 2022)\n\n### Fixed\n\n- Remove cyclic references on HTTP implementations. (#1604)\n\n### Changed\n\n- `reload_delay` default changed from `None` to `0.25` on `uvicorn.run()` and `Config`. `None` is not an acceptable value anymore. (#1545)\n\n## 0.18.2 (June 27, 2022)\n\n### Fixed\n\n- Add default `log_config` on `uvicorn.run()` (#1541)\n- Revert `logging` file name modification (#1543)\n\n## 0.18.1 (June 23, 2022)\n\n### Fixed\n\n- Use `DEFAULT_MAX_INCOMPLETE_EVENT_SIZE` as default to `h11_max_incomplete_event_size` on the CLI (#1534)\n\n## 0.18.0 (June 23, 2022)\n\n### Added\n\n- The `reload` flag prioritizes `watchfiles` instead of the deprecated `watchgod` (#1437)\n- Annotate `uvicorn.run()` function (#1423)\n- Allow configuring `max_incomplete_event_size` for `h11` implementation (#1514)\n\n### Removed\n\n- Remove `asgiref` dependency (#1532)\n\n### Fixed\n\n- Turn `raw_path` into bytes on both websockets implementations (#1487)\n- Revert log exception traceback in case of invalid HTTP request (#1518)\n- Set `asyncio.WindowsSelectorEventLoopPolicy()` when using multiple workers to avoid \"WinError 87\" (#1454)\n\n## 0.17.6 (March 11, 2022)\n\n### Changed\n\n- Change `httptools` range to `>=0.4.0` (#1400)\n\n## 0.17.5 (February 16, 2022)\n\n### Fixed\n\n- Fix case where url is fragmented in httptools protocol (#1263)\n- Fix WSGI middleware not to explode quadratically in the case of a larger body (#1329)\n\n### Changed\n\n- Send HTTP 400 response for invalid request (#1352)\n\n## 0.17.4 (February 4, 2022)\n\n### Fixed\n\n- Replace `create_server` by `create_unix_server` (#1362)\n\n## 0.17.3 (February 3, 2022)\n\n### Fixed\n\n- Drop wsproto version checking. (#1359)\n\n## 0.17.2 (February 3, 2022)\n\n### Fixed\n\n- Revert #1332. While trying to solve the memory leak, it introduced an issue (#1345) when the server receives big chunks of data using the `httptools` implementation. (#1354)\n- Revert stream interface changes. This was introduced on 0.14.0, and caused an issue (#1226), which caused a memory leak when sending TCP pings. (#1355)\n- Fix wsproto version check expression (#1342)\n\n## 0.17.1 (January 28, 2022)\n\n### Fixed\n\n- Move all data handling logic to protocol and ensure connection is closed. (#1332)\n- Change `spec_version` field from \"2.1\" to \"2.3\", as Uvicorn is compliant with that version of the ASGI specifications. (#1337)\n\n## 0.17.0.post1 (January 24, 2022)\n\n### Fixed\n\n- Add the `python_requires` version specifier (#1328)\n\n## 0.17.0 (January 14, 2022)\n\n### Added\n\n- Allow configurable websocket per-message-deflate setting (#1300)\n- Support extra_headers for WS accept message (#1293)\n- Add missing http version on websockets scope (#1309)\n\n### Fixed/Removed\n\n- Drop Python 3.6 support (#1261)\n- Fix reload process behavior when exception is raised (#1313)\n- Remove `root_path` from logs (#1294)\n\n## 0.16.0 (December 8, 2021)\n\n### Added\n\n- Enable read of uvicorn settings from environment variables (#1279)\n- Bump `websockets` to 10.0. (#1180)\n- Ensure non-zero exit code when startup fails (#1278)\n- Increase `httptools` version range from \"==0.2.*\" to \">=0.2.0,<0.4.0\". (#1243)\n- Override default asyncio event loop with reload only on Windows (#1257)\n- Replace `HttpToolsProtocol.pipeline` type from `list` to `deque`. (#1213)\n- Replace `WSGIResponder.send_queue` type from `list` to `deque`. (#1214)\n\n### Fixed\n\n- Main process exit after startup failure on reloader classes (#1177)\n- Fix the need of `httptools` on minimal installation (#1135)\n- Fix ping parameters annotation in Config class (#1127)\n\n## 0.15.0 (August 13, 2021)\n\n### Added\n\n- Change reload to be configurable with glob patterns. Currently only `.py` files are watched, which is different from the previous default behavior. (#820)\n- Add Python 3.10-rc.1 support. Now the server uses `asyncio.run` which will: start a fresh asyncio event loop, on shutdown cancel any background tasks rather than aborting them, `aexit` any remaining async generators, and shutdown the default `ThreadPoolExecutor`. (#1070)\n- Exit with status 3 when worker starts failed (#1077)\n- Add option to set websocket ping interval and timeout (#1048)\n- Adapt bind_socket to make it usable with multiple processes (#1009)\n- Add existence check to the reload directory(ies) (#1089)\n- Add missing trace log for websocket protocols (#1083)\n- Support disabling default Server and Date headers (#818)\n\n### Changed\n\n- Add PEP440 compliant version of click (#1099)\n- Bump asgiref to 3.4.0 (#1100)\n\n### Fixed\n\n- When receiving a `SIGTERM` supervisors now terminate their processes before joining them (#1069)\n- Fix `httptools` range to `>=0.4.0` (#1400)\n\n## 0.14.0 (June 1, 2021)\n\n### Added\n\n- Defaults ws max_size on server to 16MB (#995)\n- Improve user feedback if no ws library installed (#926 and #1023)\n- Support 'reason' field in 'websocket.close' messages (#957)\n- Implemented lifespan.shutdown.failed (#755)\n\n### Changed\n\n- Upgraded websockets requirements (#1065)\n- Switch to asyncio streams API (#869)\n- Update httptools from 0.1.* to 0.2.* (#1024)\n- Allow Click 8.0, refs #1016 (#1042)\n- Add search for a trusted host in ProxyHeadersMiddleware (#591)\n- Up wsproto to 1.0.0 (#892)\n\n### Fixed\n\n- Force reload_dirs to be a list (#978)\n- Fix gunicorn worker not running if extras not installed (#901)\n- Fix socket port 0 (#975)\n- Prevent garbage collection of main lifespan task (#972)\n\n## 0.13.4 (February 20, 2021)\n\n### Fixed\n\n- Fixed wsgi middleware PATH_INFO encoding (#962)\n- Fixed uvloop dependency  (#952) then (#959)\n- Relax watchgod up bound (#946)\n- Return 'connection: close' header in response (#721)\n\n### Added\n\n- Docs: Nginx + websockets (#948)\n- Document the default value of 1 for workers (#940) (#943)\n- Enabled permessage-deflate extension in websockets (#764)\n\n## 0.13.3 (December 29, 2020)\n\n### Fixed\n\n- Prevent swallowing of return codes from `subprocess` when running with Gunicorn by properly resetting signals. (#895)\n- Tweak detection of app factories to be more robust. A warning is now logged when passing a factory without the `--factory` flag. (#914)\n- Properly clean tasks when handshake is aborted when running with `--ws websockets`. (#921)\n\n## 0.13.2 (December 12, 2020)\n\n### Fixed\n\n- Log full exception traceback in case of invalid HTTP request. (#886 and #888)\n\n## 0.13.1 (December 12, 2020)\n\n### Fixed\n\n- Prevent exceptions when the ASGI application rejects a connection during the WebSocket handshake, when running on both `--ws wsproto` or `--ws websockets`. (#704 and #881)\n- Ensure connection `scope` doesn't leak in logs when using JSON log formatters. (#859 and #884)\n\n## 0.13.0 (December 8, 2020)\n\n### Added\n\n- Add `--factory` flag to support factory-style application imports. (#875)\n- Skip installation of signal handlers when not in the main thread. Allows using `Server` in multithreaded contexts without having to override `.install_signal_handlers()`. (#871)\n\n## 0.12.3 (November 21, 2020)\n\n### Fixed\n- Fix race condition that leads Quart to hang with uvicorn (#848)\n- Use latin1 when decoding X-Forwarded-* headers (#701)\n- Rework IPv6 support (#837)\n- Cancel old keepalive-trigger before setting new one. (#832)\n\n## 0.12.2 (October 19, 2020)\n\n### Added\n- Adding ability to decrypt ssl key file (#808)\n- Support .yml log config files (#799)\n- Added python 3.9 support (#804)\n\n### Fixed\n- Fixes watchgod with common prefixes (#817)\n- Fix reload with ipv6 host (#803)\n- Added cli support for headers containing colon (#813)\n- Sharing socket across workers on windows (#802)\n- Note the need to configure trusted \"ips\" when using unix sockets (#796)\n\n## 0.12.1 (September 30, 2020)\n\n### Changed\n- Pinning h11 and python-dotenv to min versions (#789)\n- Get docs/index.md in sync with README.md (#784)\n\n### Fixed\n- Improve changelog by pointing out breaking changes (#792)\n\n## 0.12.0 (September 28, 2020)\n\n### Added\n- Make reload delay configurable (#774)\n- Upgrade maximum h11 dependency version to 0.10 (#772)\n- Allow .json or .yaml --log-config files (#665)\n- Add ASGI dict to the lifespan scope (#754)\n- Upgrade wsproto to 0.15.0 (#750)\n- Use optional package installs (#666)\n\n### Changed\n- Don't set log level for root logger (#767) 8/28/20 df81b168\n- Uvicorn no longer ships extra dependencies `uvloop`, `websockets` and `httptools` as default.\n  To install these dependencies use `uvicorn[standard]`.\n\n### Fixed\n- Revert \"Improve shutdown robustness when using `--reload` or multiprocessing (#620)\" (#756)\n- Fix terminate error in windows (#744)\n- Fix bug where --log-config disables uvicorn loggers (#512)\n\n## 0.11.8 (July 30, 2020)\n\n* Fix a regression that caused Uvicorn to crash when using `--interface=wsgi`. (#730)\n* Fix a regression that caused Uvicorn to crash when using unix domain sockets. (#729)\n\n## 0.11.7 (July 28, 2020)\n\n* SECURITY FIX: Prevent sending invalid HTTP header names and values. (#725)\n* SECURITY FIX: Ensure path value is escaped before logging to the console. (#724)\n* Fix `--proxy-headers` client IP and host when using a Unix socket. (#636)\n\n## 0.11.6 (July 17, 2020)\n\n* Fix overriding the root logger.\n\n## 0.11.5 (April 29, 2020)\n\n* Revert \"Watch all files, not just .py\" due to unexpected side effects.\n* Revert \"Pass through gunicorn timeout config.\" due to unexpected side effects.\n\n## 0.11.4 (April 28, 2020)\n\n* Use `watchgod`, if installed, for watching code changes.\n* Watch all files, not just .py.\n* Pass through gunicorn timeout config.\n\n## 0.11.3 (February 17, 2020)\n\n* Update dependencies.\n\n## 0.11.2 (January 20, 2020)\n\n* Don't open socket until after application startup.\n* Support `--backlog`.\n\n## 0.11.1 (December 20, 2019)\n\n* Use a more liberal `h11` dependency. Either `0.8.*` or `0.9.*``.\n\n## 0.11.0 (December 20, 2019)\n\n* Fix reload/multiprocessing on Windows with Python 3.8.\n* Drop IOCP support. (Required for fix above.)\n* Add `uvicorn --version` flag.\n* Add `--use-colors` and `--no-use-colors` flags.\n* Display port correctly, when auto port selection isused with `--port=0`.\n\n## 0.10.8 (November 12, 2019)\n\n* Fix reload/multiprocessing error.\n\n## 0.10.7 (November 12, 2019)\n\n* Use resource_sharer.DupSocket to resolve socket sharing on Windows.\n\n## 0.10.6 (November 12, 2019)\n\n* Exit if `workers` or `reload` are use without an app import string style.\n* Reorganise supervisor processes to properly hand over sockets on windows.\n\n## 0.10.5 (November 12, 2019)\n\n* Update uvloop dependency to 0.14+\n\n## 0.10.4 (November 9, 2019)\n\n* Error clearly when `workers=<NUM>` is used with app instance, instead of an app import string.\n* Switch `--reload-dir` to current working directory by default.\n\n## 0.10.3 (November 1, 2019)\n\n* Add ``--log-level trace`\n\n## 0.10.2 (October 31, 2019)\n\n* Enable --proxy-headers by default.\n\n## 0.10.1 (October 31, 2019)\n\n* Resolve issues with logging when using `--reload` or `--workers`.\n* Setup up root logger to capture output for all logger instances, not just `uvicorn.error` and `uvicorn.access`.\n\n## 0.10.0 (October 29, 2019)\n\n* Support for Python 3.8\n* Separated out `uvicorn.error` and `uvicorn.access` logs.\n* Coloured log output when connected to a terminal.\n* Dropped `logger=` config setting.\n* Added `--log-config [FILE]` and `log_config=[str|dict]`. May either be a Python logging config dictionary or the file name of a logging configuration.\n* Added `--forwarded_allow_ips` and `forwarded_allow_ips`. Defaults to the value of the `$FORWARDED_ALLOW_IPS` environment variable or \"127.0.0.1\". The `--proxy-headers` flag now defaults to `True`, but only trusted IPs are used to populate forwarding info.\n* The `--workers` setting now defaults to the value of the `$WEB_CONCURRENCY` environment variable.\n* Added support for `--env-file`. Requires `python-dotenv`.\n"
  },
  {
    "path": "docs/server-behavior.md",
    "content": "# Server Behavior\n\nUvicorn is designed with particular attention to connection and resource management, in order to provide a robust server implementation. It aims to ensure graceful behavior to either server or client errors, and resilience to poor client behavior or denial of service attacks.\n\n## HTTP Headers\n\nThe `Server` and `Date` headers are added to all outgoing requests.\n\nIf a `Connection: Close` header is included then Uvicorn will close the connection after the response. Otherwise connections will stay open, pending the keep-alive timeout.\n\nIf a `Content-Length` header is included then Uvicorn will ensure that the content length of the response body matches the value in the header, and raise an error otherwise.\n\nIf no `Content-Length` header is included then Uvicorn will use chunked encoding for the response body, and will set a `Transfer-Encoding` header if required.\n\nIf a `Transfer-Encoding` header is included then any `Content-Length` header will be ignored.\n\nHTTP headers are mandated to be case-insensitive. Uvicorn will always send response headers strictly in lowercase.\n\n---\n\n## Flow Control\n\nProper flow control ensures that large amounts of data do not become buffered on the transport when either side of a connection is sending data faster than its counterpart is able to handle.\n\n### Write flow control\n\nIf the write buffer passes a high water mark, then Uvicorn ensures the ASGI `send` messages will only return once the write buffer has been drained below the low water mark.\n\n### Read flow control\n\nUvicorn will pause reading from a transport once the buffered request body hits a high water mark, and will only resume once `receive` has been called, or once the response has been sent.\n\n---\n\n## Request and Response bodies\n\n### Response completion\n\nOnce a response has been sent, Uvicorn will no longer buffer any remaining request body. Any later calls to `receive` will return an `http.disconnect` message.\n\nTogether with the read flow control, this behavior ensures that responses that return without reading the request body will not stream any substantial amounts of data into memory.\n\n### Expect: 100-Continue\n\nThe `Expect: 100-Continue` header may be sent by clients to require a confirmation from the server before uploading the request body. This can be used to ensure that large request bodies are only sent once the client has confirmation that the server is willing to accept the request.\n\nUvicorn ensures that any required `100 Continue` confirmations are only sent if the ASGI application calls `receive` to read the request body.\n\nNote that proxy configurations may not necessarily forward on `Expect: 100-Continue` headers. In particular, Nginx defaults to buffering request bodies, and automatically sends `100 Continues` rather than passing the header on to the upstream server.\n\n### HEAD requests\n\nUvicorn will strip any response body from HTTP requests with the `HEAD` method.\n\nApplications should generally treat `HEAD` requests in the same manner as `GET` requests, in order to ensure that identical headers are sent in both cases, and that any ASGI middleware that modifies the headers will operate identically in either case.\n\nOne exception to this might be if your application serves large file downloads, in which case you might wish to only generate the response headers.\n\n---\n\n## Timeouts\n\nUvicorn provides the following timeouts:\n\n* Keep-Alive. Defaults to 5 seconds. Between requests, connections must receive new data within this period or be disconnected.\n\n---\n\n## Resource Limits\n\nUvicorn provides the following resource limiting:\n\n* Concurrency. Defaults to `None`. If set, this provides a maximum number of concurrent tasks *or* open connections that should be allowed. Any new requests or connections that occur once this limit has been reached will result in a \"503 Service Unavailable\" response. Setting this value to a limit that you know your servers are able to support will help ensure reliable resource usage, even against significantly over-resourced servers.\n* Max requests. Defaults to `None`. If set, this provides a maximum number of HTTP requests that will be serviced before terminating a process. Together with a process manager this can be used to prevent memory leaks from impacting long running processes.\n\n---\n\n## Server Errors\n\nServer errors will be logged at the `error` log level. All logging defaults to being written to `stdout`.\n\n### Exceptions\n\nIf an exception is raised by an ASGI application, and a response has not yet been sent on the connection, then a `500 Server Error` HTTP response will be sent.\n\nUvicorn sends the headers and the status code as soon as it receives from the ASGI application. This means that if the application sends a [Response Start](https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event)\nmessage with a status code of `200 OK`, and then an exception is raised, the response will still be sent with a status code of `200 OK`.\n\n### Invalid responses\n\nUvicorn will ensure that ASGI applications send the correct sequence of messages, and will raise errors otherwise. This includes checking for no response sent, partial response sent, or invalid message sequences being sent.\n\n---\n\n## Graceful Process Shutdown\n\nGraceful process shutdowns are particularly important during a restart period. During this period you want to:\n\n* Start a number of new server processes to handle incoming requests, listening on the existing socket.\n* Stop the previous server processes from listening on the existing socket.\n* Close any connections that are not currently waiting on an HTTP response, and wait for any other connections to finalize their HTTP responses.\n* Wait for any background tasks to run to completion, such as occurs when the ASGI application has sent the HTTP response, but the asyncio task has not yet run to completion.\n\nUvicorn handles process shutdown gracefully, ensuring that connections are properly finalized, and all tasks have run to completion. During a shutdown period Uvicorn will ensure that responses and tasks must still complete within the configured timeout periods.\n\n---\n\n## HTTP Pipelining\n\nHTTP/1.1 provides support for sending multiple requests on a single connection, before having received each corresponding response. Servers are required to support HTTP pipelining, but it is now generally accepted to lead to implementation issues. It is not enabled on browsers, and may not necessarily be enabled on any proxies that the HTTP request passes through.\n\nUvicorn supports pipelining pragmatically. It will queue up any pipelined HTTP requests, and pause reading from the underlying transport. It will not start processing pipelined requests until each response has been dealt with in turn.\n"
  },
  {
    "path": "docs/settings.md",
    "content": "# Settings\n\nUse the following options to configure Uvicorn, when running from the command line.\n\n## Configuration Methods\n\nThere are three ways to configure Uvicorn:\n\n1. **Command Line**: Use command line options when running Uvicorn directly.\n   ```bash\n   uvicorn main:app --host 0.0.0.0 --port 8000\n   ```\n\n2. **Programmatic**: Use keyword arguments when running programmatically with `uvicorn.run()`.\n   ```python\n   uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8000)\n   ```\n\n    !!! note\n        When using `reload=True` or `workers=NUM`, you should put `uvicorn.run` into\n        an `if __name__ == '__main__'` clause in the main module.\n\n3. **Environment Variables**: Use environment variables with the prefix `UVICORN_`.\n   ```bash\n   export UVICORN_HOST=\"0.0.0.0\"\n   export UVICORN_PORT=\"8000\"\n   uvicorn main:app\n   ```\n\nCLI options and the arguments for `uvicorn.run()` take precedence over environment variables.\n\nAlso note that `UVICORN_*` prefixed settings cannot be used from within an environment\nconfiguration file. Using an environment configuration file with the `--env-file` flag is\nintended for configuring the ASGI application that uvicorn runs, rather than configuring\nuvicorn itself.\n\n## Application\n\n* `APP` - The ASGI application to run, in the format `\"<module>:<attribute>\"`.\n* `--factory` - Treat `APP` as an application factory, i.e. a `() -> <ASGI app>` callable.\n* `--app-dir <path>` - Look for APP in the specified directory by adding it to the PYTHONPATH. **Default:** *Current working directory*.\n\n## Socket Binding\n\n* `--host <str>` - Bind socket to this host. Use `--host 0.0.0.0` to make the application available on your local network. IPv6 addresses are supported, for example: `--host '::'`. **Default:** *'127.0.0.1'*.\n* `--port <int>` - Bind to a socket with this port. If set to 0, an available port will be picked. **Default:** *8000*.\n* `--uds <path>` - Bind to a UNIX domain socket, for example `--uds /tmp/uvicorn.sock`. Useful if you want to run Uvicorn behind a reverse proxy.\n* `--fd <int>` - Bind to socket from this file descriptor. Useful if you want to run Uvicorn within a process manager.\n\n## Development\n\n* `--reload` - Enable auto-reload. Uvicorn supports two versions of auto-reloading behavior enabled by this option. **Default:** *False*.\n* `--reload-dir <path>` - Specify which directories to watch for python file changes. May be used multiple times. If unused, then by default the whole current directory will be watched. If you are running programmatically use `reload_dirs=[]` and pass a list of strings.\n* `--reload-delay <float>` - Delay between previous and next check if application needs to be reloaded. **Default:** *0.25*.\n\n### Reloading without watchfiles\n\nIf Uvicorn _cannot_ load [watchfiles](https://pypi.org/project/watchfiles/) at runtime, it will periodically look for changes in modification times to all `*.py` files (and only `*.py` files) inside of its monitored directories. See the `--reload-dir` option. Specifying other file extensions is not supported unless watchfiles is installed. See the `--reload-include` and `--reload-exclude` options for details.\n\n### Reloading with watchfiles\n\nFor more nuanced control over which file modifications trigger reloads, install `uvicorn[standard]`, which includes watchfiles as a dependency. Alternatively, install [watchfiles](https://pypi.org/project/watchfiles/) where Uvicorn can see it.\n\nUsing Uvicorn with watchfiles will enable the following options (which are otherwise ignored):\n\n* `--reload-include <glob-pattern>` - Specify a glob pattern to match files or directories which will be watched. May be used multiple times. By default the following patterns are included: `*.py`. These defaults can be overwritten by including them in `--reload-exclude`.\n* `--reload-exclude <glob-pattern>` - Specify a glob pattern to match files or directories which will excluded from watching. May be used multiple times. By default the following patterns are excluded: `.*, .py[cod], .sw.*, ~*`. These defaults can be overwritten by including them in `--reload-include`.\n\n!!! tip\n    When using Uvicorn through [WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux), you might\n    have to set the `WATCHFILES_FORCE_POLLING` environment variable, for file changes to trigger a reload.\n    See [watchfiles documentation](https://watchfiles.helpmanual.io/api/watch/) for further details.\n\n## Production\n\n* `--workers <int>` - Number of worker processes. Defaults to the `$WEB_CONCURRENCY` environment variable if available, or 1. Not valid with `--reload`.\n* `--env-file <path>` - Environment configuration file for the ASGI application. **Default:** *None*.\n* `--timeout-worker-healthcheck <int>` - Maximum number of seconds to wait for a worker to respond to a healthcheck. **Default:** *5*.\n\n!!! note\n    The `--reload` and `--workers` arguments are mutually exclusive. You cannot use both at the same time.\n\n## Logging\n\n* `--log-config <path>` - Logging configuration file. **Options:** *`dictConfig()` formats: .json, .yaml*. Any other format will be processed with `fileConfig()`. Set the `formatters.default.use_colors` and `formatters.access.use_colors` values to override the auto-detected behavior.\n    * If you wish to use a YAML file for your logging config, you will need to include PyYAML as a dependency for your project or install uvicorn with the `[standard]` optional extras.\n* `--log-level <str>` - Set the log level. **Options:** *'critical', 'error', 'warning', 'info', 'debug', 'trace'.* **Default:** *'info'*.\n* `--no-access-log` - Disable access log only, without changing log level.\n* `--use-colors / --no-use-colors` - Enable / disable colorized formatting of the log records. If not set, colors will be auto-detected. This option is ignored if the `--log-config` CLI option is used.\n\n## Implementation\n\n* `--loop <str>` - Set the event loop implementation. The uvloop implementation provides greater performance, but is not compatible with Windows or PyPy. **Options:** *'auto', 'asyncio', 'uvloop'.* **Default:** *'auto'*.\n* `--http <str>` - Set the HTTP protocol implementation. The httptools implementation provides greater performance, but it not compatible with PyPy. **Options:** *'auto', 'h11', 'httptools'.* **Default:** *'auto'*.\n* `--ws <str>` - Set the WebSockets protocol implementation. Either of the `websockets` and `wsproto` packages are supported. There are two versions of `websockets` supported: `websockets` and `websockets-sansio`. Use `'none'` to ignore all websocket requests. **Options:** *'auto', 'none', 'websockets', 'websockets-sansio', 'wsproto'.* **Default:** *'auto'*.\n* `--ws-max-size <int>` - Set the WebSockets max message size, in bytes. Only available with the `websockets` protocol. **Default:** *16777216* (16 MB).\n* `--ws-max-queue <int>` - Set the maximum length of the WebSocket incoming message queue. Only available with the `websockets` protocol. **Default:** *32*.\n* `--ws-ping-interval <float>` - Set the WebSockets ping interval, in seconds. Only available with the `websockets` protocol. **Default:** *20.0*.\n* `--ws-ping-timeout <float>` - Set the WebSockets ping timeout, in seconds. Only available with the `websockets` protocol. **Default:** *20.0*.\n* `--ws-per-message-deflate <bool>` - Enable/disable WebSocket per-message-deflate compression. Only available with the `websockets` protocol. **Default:** *True*.\n* `--lifespan <str>` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*.\n* `--h11-max-incomplete-event-size <int>` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *16384* (16 KB).\n\n## Application Interface\n\n* `--interface <str>` - Select ASGI3, ASGI2, or WSGI as the application interface.\nNote that WSGI mode always disables WebSocket support, as it is not supported by the WSGI interface.\n**Options:** *'auto', 'asgi3', 'asgi2', 'wsgi'.* **Default:** *'auto'*.\n\n!!! warning\n    Uvicorn's native WSGI implementation is deprecated, you should switch\n    to [a2wsgi](https://github.com/abersheeran/a2wsgi) (`pip install a2wsgi`).\n\n## HTTP\n\n* `--root-path <str>` - Set the ASGI `root_path` for applications submounted below a given URL path. **Default:** *\"\"*.\n* `--proxy-headers / --no-proxy-headers` - Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate remote address info. Defaults to enabled, but is restricted to only trusting connecting IPs in the `forwarded-allow-ips` configuration.\n* `--forwarded-allow-ips <comma-separated-list>` - Comma separated list of IP Addresses, IP Networks, or literals (e.g. UNIX Socket path) to trust with proxy headers. Defaults to the `$FORWARDED_ALLOW_IPS` environment variable if available, or '127.0.0.1'. The literal `'*'` means trust everything.\n* `--server-header / --no-server-header` - Enable/Disable default `Server` header. **Default:** *True*.\n* `--date-header / --no-date-header` - Enable/Disable default `Date` header. **Default:** *True*.\n* `--header <name:value>` - Specify custom default HTTP response headers as a Name:Value pair. May be used multiple times.\n\n!!! note\n    The `--no-date-header` flag doesn't have effect on the `websockets` implementation.\n\n## HTTPS\n\nThe [SSL context](https://docs.python.org/3/library/ssl.html#ssl.SSLContext) can be configured with the following options:\n\n* `--ssl-keyfile <path>` - The SSL key file.\n* `--ssl-keyfile-password <str>` - The password to decrypt the ssl key.\n* `--ssl-certfile <path>` - The SSL certificate file.\n* `--ssl-version <int>` - The SSL version to use. **Default:** *ssl.PROTOCOL_TLS_SERVER*.\n* `--ssl-cert-reqs <int>` - Whether client certificate is required. **Default:** *ssl.CERT_NONE*.\n* `--ssl-ca-certs <str>` - The CA certificates file.\n* `--ssl-ciphers <str>` - The ciphers to use. **Default:** *\"TLSv1\"*.\n\nTo understand more about the SSL context options, please refer to the [Python documentation](https://docs.python.org/3/library/ssl.html).\n\n## Resource Limits\n\n* `--limit-concurrency <int>` - Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses. Useful for ensuring known memory usage patterns even under over-resourced loads.\n* `--limit-max-requests <int>` - Maximum number of requests to service before terminating the process. Useful when running together with a process manager, for preventing memory leaks from impacting long-running processes.\n* `--limit-max-requests-jitter <int>` - Maximum jitter to add to `limit-max-requests`. Each worker adds a random number in the range `[0, jitter]`, staggering restarts to avoid all workers restarting simultaneously. **Default:** *0*.\n* `--backlog <int>` - Maximum number of connections to hold in backlog. Relevant for heavy incoming traffic. **Default:** *2048*.\n\n## Timeouts\n\n* `--timeout-keep-alive <int>` - Close Keep-Alive connections if no new data is received within this timeout (in seconds). **Default:** *5*.\n* `--timeout-graceful-shutdown <int>` - Maximum number of seconds to wait for graceful shutdown. After this timeout, the server will start terminating requests.\n"
  },
  {
    "path": "docs/sponsorship.md",
    "content": "# ✨ Sponsor Starlette & Uvicorn ✨\n\nThank you for your interest in sponsoring Starlette and Uvicorn! ❤️\n\nYour support *directly* contributes to the ongoing development, maintenance, and long-term sustainability of both projects.\n\n<div style=\"display: flex; justify-content: center; gap: 4rem; margin: 2rem 0; text-align: center;\">\n    <div style=\"padding: 1rem;\">\n        <h3 style=\"color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;\">67M+</h3>\n        <p>Starlette Downloads/Month</p>\n    </div>\n    <div style=\"padding: 1rem;\">\n        <h3 style=\"color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;\">57M+</h3>\n        <p>Uvicorn Downloads/Month</p>\n    </div>\n    <div style=\"padding: 1rem;\">\n        <h3 style=\"color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;\">19K+</h3>\n        <p>Combined GitHub Stars</p>\n    </div>\n</div>\n\n## Why Sponsor?\n\nWhile Starlette and Uvicorn are part of the [Encode](https://github.com/encode) organization,\nthey have been primarily maintained by [**Marcelo Trylesinski (Kludex)**](https://github.com/Kludex)\nfor the past several years. His dedication and consistent work have been instrumental in keeping\nthese projects robust, secure, and up-to-date.\n\nThis sponsorship page was created to give the community an opportunity to support Marcelo's continued\nefforts in maintaining and improving both projects. Your sponsorship directly enables him to\ndedicate more time and resources to maintaining and improving these essential tools:\n\n- [x] **Active Development:** Developing new features, enhancing existing ones, and\n  keeping both projects aligned with the latest developments in the Python and ASGI ecosystems. 💻\n- [x] **Community Support:** Providing better support, addressing user issues,\n  and cultivating a welcoming environment for contributors. 🤝\n- [x] **Long-Term Stability:** Ensuring the long-term viability of both projects through strategic\n  planning and addressing technical debt. 🌳\n- [x] **Bug Fixes & Maintenance:** Providing prompt attention to bug reports and\n  general maintenance to keep the projects reliable. 🔨\n- [x] **Security:** Ensuring robust security practices, conducting regular security audits, and\n  promptly addressing vulnerabilities to protect millions of production deployments. 🔒\n- [x] **Documentation:** Creating comprehensive guides, tutorials, and examples to help users of all skill levels. 📖\n\n## How Sponsorship Works\n\nWe currently manage sponsorships *exclusively* through **GitHub Sponsors**. This platform integrates seamlessly with the GitHub ecosystem, making it easy for organizations to contribute.\n\n<div style=\"text-align: center; padding: 2rem; margin: 2rem 0; background: linear-gradient(135deg, #6e5494, #24292e); border-radius: 10px; color: white;\">\n    <h2 style=\"color: white; margin-bottom: 1rem;\">🌟 Become a Sponsor Today! 🌟</h2>\n    <p style=\"margin-bottom: 1.5rem; font-size: 1.1em;\">Your support helps keep Starlette and Uvicorn growing stronger!</p>\n    <a href=\"https://github.com/sponsors/Kludex\"\n       style=\"display: inline-block; padding: 1rem 2rem; background-color: #238636; color: white; text-decoration: none; border-radius: 6px; font-size: 1.2em; font-weight: bold; transition: all 0.3s ease-in-out;\"\n       onmouseover=\"this.style.backgroundColor='#2ea043';this.style.transform='translateY(-2px)'\"\n       onmouseout=\"this.style.backgroundColor='#238636';this.style.transform='translateY(0)'\">\n        ❤️ Sponsor on GitHub\n    </a>\n</div>\n\n## Sponsorship Tiers 🎁\n\n<div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin: 2rem 0;\">\n    <div style=\"padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; display: flex; flex-direction: column;\">\n        <h3 style=\"color: #cd7f32;\">🥉 Bronze Sponsor</h3>\n        <div style=\"font-size: 1.5em; margin: 1rem 0;\">$100<span style=\"font-size: 0.6em;\">/month</span></div>\n        <ul style=\"list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;\">\n            <li>✓ Company name on Sponsors page</li>\n            <li>✓ Small logo with link</li>\n            <li>✓ Our eternal gratitude</li>\n        </ul>\n        <div style=\"text-align: center; margin-top: auto;\">\n            <a href=\"https://github.com/sponsors/Kludex\" style=\"display: inline-block; padding: 0.5rem 1rem; background-color: #cd7f32; color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;\" onmouseover=\"this.style.opacity='0.8'\" onmouseout=\"this.style.opacity='1'\">\n                Become a Bronze Sponsor\n            </a>\n        </div>\n    </div>\n    <div style=\"padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; display: flex; flex-direction: column;\">\n        <h3 style=\"color: #c0c0c0;\">🥈 Silver Sponsor</h3>\n        <div style=\"font-size: 1.5em; margin: 1rem 0;\">$250<span style=\"font-size: 0.6em;\">/month</span></div>\n        <ul style=\"list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;\">\n            <li>✓ All Bronze benefits</li>\n            <li>✓ Medium-sized logo</li>\n            <li>✓ Release notes mention</li>\n        </ul>\n        <div style=\"text-align: center; margin-top: auto;\">\n            <a href=\"https://github.com/sponsors/Kludex\" style=\"display: inline-block; padding: 0.5rem 1rem; background-color: #c0c0c0; color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;\" onmouseover=\"this.style.opacity='0.8'\" onmouseout=\"this.style.opacity='1'\">\n                Become a Silver Sponsor\n            </a>\n        </div>\n    </div>\n    <div style=\"padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; position: relative; overflow: hidden; display: flex; flex-direction: column;\">\n        <div style=\"position: absolute; top: 10px; right: -25px; background: #238636; color: white; padding: 5px 30px; transform: rotate(45deg);\">\n            Popular\n        </div>\n        <h3 style=\"color: #ffd700;\">🥇 Gold Sponsor</h3>\n        <div style=\"font-size: 1.5em; margin: 1rem 0;\">$500<span style=\"font-size: 0.6em;\">/month</span></div>\n        <ul style=\"list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;\">\n            <li>✓ All Silver benefits</li>\n            <li>✓ Large logo on main pages</li>\n            <li>✓ Priority support</li>\n        </ul>\n        <div style=\"text-align: center; margin-top: auto;\">\n            <a href=\"https://github.com/sponsors/Kludex\" style=\"display: inline-block; padding: 0.5rem 1rem; background-color: #ffd700; color: black; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;\" onmouseover=\"this.style.opacity='0.8'\" onmouseout=\"this.style.opacity='1'\">\n                Become a Gold Sponsor\n            </a>\n        </div>\n    </div>\n</div>\n\n<div style=\"text-align: center; margin: 2rem 0;\">\n    <h3>🤝 Custom Sponsor</h3>\n    <p>Looking for something different? <a href=\"mailto:marcelotryle@gmail.com\">Contact us</a> to discuss custom sponsorship options!</p>\n</div>\n\n## Current Sponsors\n\n**Thank you to our generous sponsors!** 🙏\n\n<div style=\"display: flex; flex-direction: column; gap: 3rem; margin: 2rem 0;\">\n    <div>\n        <h3 style=\"text-align: center; color: #ffd700; margin-bottom: 1.5rem;\">🏆 Gold Sponsors</h3>\n        <div style=\"display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;\">\n            <a href=\"https://fastapi.tiangolo.com\" style=\"text-decoration: none;\">\n                <div style=\"width: 200px; background: #f6f8fa; border-radius: 8px; padding: 1rem; text-align: center;\">\n                    <div style=\"height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 0.75rem;\">\n                        <img src=\"https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png\" alt=\"FastAPI\" style=\"max-width: 100%; max-height: 100%; object-fit: contain;\">\n                    </div>\n                    <p style=\"margin: 0; color: #57606a; font-size: 0.9em;\">Modern, fast web framework for building APIs with Python 3.8+</p>\n                </div>\n            </a>\n        </div>\n    </div>\n\n    <div>\n        <h3 style=\"text-align: center; color: #c0c0c0; margin-bottom: 1.5rem;\">🥈 Silver Sponsors</h3>\n        <div style=\"display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;\">\n            <!-- Add Silver Sponsors here -->\n        </div>\n    </div>\n\n    <div>\n        <h3 style=\"text-align: center; color: #cd7f32; margin-bottom: 1.5rem;\">🥉 Bronze Sponsors</h3>\n        <div style=\"display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;\">\n            <!-- Add Bronze Sponsors here -->\n        </div>\n    </div>\n</div>\n\n## Alternative Sponsorship Platforms\n\n<div style=\"background: #f6f8fa; padding: 1.5rem; border-radius: 8px; margin: 2rem 0;\">\n    <h3>📢 We Want Your Input!</h3>\n    <p>We are currently evaluating whether to expand our sponsorship options beyond GitHub Sponsors. If your company would be interested in sponsoring Starlette and Uvicorn but prefers to use a different platform (e.g., Open Collective, direct invoicing), please let us know!</p>\n    <p>Your feedback is invaluable in helping us make sponsorship as accessible as possible. Share your thoughts by:</p>\n    <ul>\n        <li>Opening a discussion on our <a href=\"https://github.com/Kludex/starlette/discussions\">GitHub repository</a></li>\n        <li>Contacting us directly at <a href=\"mailto:marcelotryle@gmail.com\">marcelotryle@gmail.com</a></li>\n    </ul>\n</div>\n\n<a id=\"acknowledgments\"></a>\n\n## Community & Future Plans 🌟\n\nWe want to express our deepest gratitude to all the contributors who have helped shape Starlette and\nUvicorn over the years. These projects wouldn't be what they are today without the incredible work of\nevery single contributor.\n\nSpecial thanks to some of our most impactful contributors:\n\n- **Tom Christie** ([@tomchristie](https://github.com/tomchristie)) - The original creator of Starlette and Uvicorn.\n- **Adrian Garcia Badaracco** ([@adriangb](https://github.com/adriangb)) - Major contributor to Starlette.\n- **Thomas Grainger** ([@graingert](https://github.com/graingert)) - Major contributor to AnyIO, and significant contributions to Starlette and Uvicorn.\n- **Alex Grönholm** ([@agronholm](https://github.com/agronholm)) - Creator of AnyIO.\n- **Florimond Manca** ([@florimondmanca](https://github.com/florimondmanca)) - Important contributions to Starlette and Uvicorn.\n\nIf you want your name removed from the list above, or if I forgot a significant contributor, please let me know.\nYou can view all contributors on GitHub:\n[Starlette Contributors](https://github.com/Kludex/starlette/graphs/contributors) / [Uvicorn Contributors](https://github.com/Kludex/uvicorn/graphs/contributors).\n\nWhile the current sponsorship program directly supports Marcelo's maintenance work, we are exploring ways\nto distribute funding to other key contributors in the future. This initiative is still in early planning\nstages, as we want to ensure a fair and sustainable model that recognizes the valuable contributions of\nour community members."
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Uvicorn\nsite_description: The lightning-fast ASGI server.\nsite_url: https://uvicorn.dev\n\nrepo_name: Kludex/uvicorn\nrepo_url: https://github.com/Kludex/uvicorn\nedit_uri: edit/main/docs/\n\nstrict: true\n\ntheme:\n  name: material\n  custom_dir: docs/overrides\n  logo: uvicorn.png\n  favicon: uvicorn.png\n  palette:\n    - scheme: \"default\"\n      media: \"(prefers-color-scheme: light)\"\n      toggle:\n        icon: \"material/lightbulb\"\n        name: \"Switch to dark mode\"\n    - scheme: \"slate\"\n      media: \"(prefers-color-scheme: dark)\"\n      primary: \"blue\"\n      toggle:\n        icon: \"material/lightbulb-outline\"\n        name: \"Switch to light mode\"\n  icon:\n    repo: fontawesome/brands/github\n  features:\n    - content.code.annotate\n    - content.code.copy # https://squidfunk.github.io/mkdocs-material/upgrade/?h=content+copy#contentcodecopy\n    - content.tabs.link\n    - navigation.footer # https://squidfunk.github.io/mkdocs-material/upgrade/?h=content+copy#navigationfooter\n    - navigation.path\n    - navigation.sections # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation\n    - navigation.top # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#back-to-top-button\n    - navigation.tracking\n    - search.suggest\n    - search.highlight\n    - toc.follow # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#anchor-following\n\n# https://www.mkdocs.org/user-guide/configuration/#validation\nvalidation:\n  omitted_files: warn\n  absolute_links: warn\n  unrecognized_links: warn\n\nnav:\n  - Welcome: index.md\n  - Installation: installation.md\n  - Settings: settings.md\n  - Server Behavior: server-behavior.md\n  - Concepts:\n      - ASGI: concepts/asgi.md\n      - Lifespan: concepts/lifespan.md\n      - WebSockets: concepts/websockets.md\n      - Event Loop: concepts/event-loop.md\n  - Deployment:\n      - Deployment: deployment/index.md\n      - Docker: deployment/docker.md\n  - Release Notes: release-notes.md\n  - Contributing: contributing.md\n  - Sponsorship: sponsorship.md\n\nextra:\n  analytics:\n    provider: google\n    property: G-KTS6TXPD85\n  social:\n    - icon: fontawesome/brands/github-alt\n      link: https://github.com/Kludex/uvicorn\n    - icon: fontawesome/brands/discord\n      link: https://discord.com/invite/RxKUF5JuHs\n    - icon: fontawesome/brands/twitter\n      link: https://x.com/marcelotryle\n    - icon: fontawesome/brands/linkedin\n      link: https://www.linkedin.com/in/marcelotryle\n    - icon: fontawesome/solid/globe\n      link: https://fastapiexpert.com\n\nmarkdown_extensions:\n  - attr_list\n  - admonition\n  - codehilite:\n      css_class: highlight\n  - toc:\n      permalink: true\n  - pymdownx.details\n  - pymdownx.inlinehilite\n  - pymdownx.snippets\n  - pymdownx.superfences\n  - pymdownx.tabbed:\n      alternate_style: true\n  - pymdownx.emoji:\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n  - pymdownx.tasklist:\n      custom_checkbox: true\n  - pymdownx.extra:\n      pymdownx.superfences:\n        custom_fences:\n          - name: mermaid\n            class: mermaid\n            format: !!python/name:pymdownx.superfences.fence_code_format\n\nplugins:\n  - search\n  - mkdocstrings:\n      handlers:\n        python:\n          inventories:\n            - https://docs.python.org/3/objects.inv\n  - llmstxt:\n      full_output: llms-full.txt\n      markdown_description: |-\n        Uvicorn is a lightning-fast ASGI server implementation, designed to run asynchronous web applications.\n        It supports the ASGI specification, which allows for both HTTP/1.1 and WebSocket protocols.\n      sections:\n        Sections:\n          - index.md\n          - settings.md\n          - deployment/*.md\n          - server-behavior.md\n        Concepts:\n          - concepts/*.md\n\nhooks:\n  - docs/plugins/main.py\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"uvicorn\"\ndynamic = [\"version\"]\ndescription = \"The lightning-fast ASGI server.\"\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nlicense-files = [\"LICENSE.md\"]\nrequires-python = \">=3.10\"\nauthors = [\n    { name = \"Tom Christie\", email = \"tom@tomchristie.com\" },\n]\nmaintainers = [\n    { name = \"Marcelo Trylesinski\", email = \"marcelotryle@gmail.com\" },\n]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Environment :: Web Environment\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n    \"Topic :: Internet :: WWW/HTTP\",\n]\ndependencies = [\n    \"click>=7.0\",\n    \"h11>=0.8\",\n    \"typing_extensions>=4.0; python_version < '3.11'\",\n]\n\n[project.optional-dependencies]\nstandard = [\n    \"colorama>=0.4; sys_platform == 'win32'\",\n    \"httptools>=0.6.3\",\n    \"python-dotenv>=0.13\",\n    \"PyYAML>=5.1\",\n    \"uvloop>=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')\",\n    \"watchfiles>=0.20\",\n    \"websockets>=10.4\",\n]\n\n[dependency-groups]\ndev = [\n    # We add uvicorn[standard] so `uv sync` considers the extras.\n    \"uvicorn[standard]\",\n    \"ruff==0.15.1\",\n    \"pytest==9.0.2\",\n    \"pytest-mock==3.15.1\",\n    \"pytest-xdist[psutil]==3.8.0\",\n    \"pytest-codspeed>=4.1.1\",\n    \"mypy==1.19.1\",\n    \"types-click==7.1.8\",\n    \"types-pyyaml==6.0.12.20250915\",\n    \"trustme==1.2.1\",\n    \"cryptography>=44.0.3\",\n    \"coverage==7.13.4\",\n    \"coverage-conditional-plugin==0.9.0\",\n    \"coverage-enable-subprocess==1.0\",\n    \"httpx==0.28.1\",\n    # check dist\n    \"twine==6.2.0\",\n    # Explicit optionals,\n    \"a2wsgi==1.10.10\",\n    \"wsproto==1.3.2\",\n    \"websockets==13.1\",\n]\ndocs = [\n    \"mkdocs==1.6.1\",\n    \"mkdocs-material==9.7.1\",\n    \"mkdocstrings-python==2.0.2\",\n    \"mkdocs-llmstxt==0.5.0\",\n]\n\n[tool.uv]\ndefault-groups = [\"dev\", \"docs\"]\nrequired-version = \">=0.8.6\"\n\n[project.scripts]\nuvicorn = \"uvicorn.main:main\"\n\n[project.urls]\nChangelog = \"https://uvicorn.dev/release-notes\"\nFunding = \"https://github.com/sponsors/encode\"\nHomepage = \"https://uvicorn.dev/\"\nSource = \"https://github.com/Kludex/uvicorn\"\n\n[tool.hatch.version]\npath = \"uvicorn/__init__.py\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\"/uvicorn\", \"/tests\"]\n\n[tool.ruff]\nline-length = 120\n\n[tool.ruff.lint]\nselect = [\"E\", \"F\", \"I\", \"FA\", \"UP\"]\nignore = [\"B904\", \"B028\", \"UP031\"]\n\n[tool.ruff.lint.isort]\ncombine-as-imports = true\n\n[tool.mypy]\nwarn_unused_ignores = true\nwarn_redundant_casts = true\nshow_error_codes = true\ndisallow_untyped_defs = false\nignore_missing_imports = true\nfollow_imports = \"silent\"\n\n[tool.pytest.ini_options]\naddopts = \"-rxXs --strict-config --strict-markers -n 8\"\nxfail_strict = true\nfilterwarnings = [\n    \"error\",\n    \"ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning\",\n    \"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning\",\n    \"ignore: remove second argument of ws_handler:DeprecationWarning:websockets\",\n    \"ignore: websockets.legacy is deprecated.*:DeprecationWarning\",\n    \"ignore: websockets.server.WebSocketServerProtocol is deprecated.*:DeprecationWarning\",\n    \"ignore: websockets.client.connect is deprecated.*:DeprecationWarning\",\n    # httptools in Python 3.14t needs the `PYTHON_GIL=0` environment variable, or raises a RuntimeWarning.\n    \"ignore: The global interpreter lock (GIL)*:RuntimeWarning\"\n]\n\n[tool.coverage.run]\nparallel = true\nsource_pkgs = [\"uvicorn\", \"tests\"]\nplugins = [\"coverage_conditional_plugin\"]\nomit = [\"uvicorn/workers.py\", \"uvicorn/__main__.py\", \"uvicorn/_compat.py\", \"tests/benchmarks/*\"]\n\n[tool.coverage.report]\nprecision = 2\nfail_under = 100\nshow_missing = true\nskip_covered = true\nexclude_lines = [\n    \"pragma: no cover\",\n    \"pragma: nocover\",\n    \"pragma: full coverage\",\n    \"if TYPE_CHECKING:\",\n    \"if typing.TYPE_CHECKING:\",\n    \"raise NotImplementedError\",\n]\n\n[tool.coverage.coverage_conditional_plugin.omit]\n\"sys_platform == 'win32'\" = [\n    \"uvicorn/loops/uvloop.py\",\n    \"uvicorn/supervisors/multiprocess.py\",\n    \"tests/supervisors/test_multiprocess.py\",\n]\n\"sys_platform != 'win32'\" = [\"uvicorn/loops/asyncio.py\"]\n\n[tool.coverage.coverage_conditional_plugin.rules]\npy-win32 = \"sys_platform == 'win32'\"\npy-not-win32 = \"sys_platform != 'win32'\"\npy-linux = \"sys_platform == 'linux'\"\npy-not-linux = \"sys_platform != 'linux'\"\npy-darwin = \"sys_platform == 'darwin'\"\npy-gte-39 = \"sys_version_info >= (3, 9)\"\npy-lt-39 = \"sys_version_info < (3, 9)\"\npy-gte-310 = \"sys_version_info >= (3, 10)\"\npy-lt-310 = \"sys_version_info < (3, 10)\"\npy-gte-311 = \"sys_version_info >= (3, 11)\"\npy-lt-311 = \"sys_version_info < (3, 11)\"\n"
  },
  {
    "path": "scripts/build",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv build\nuv run twine check dist/*\nuv run mkdocs build\n"
  },
  {
    "path": "scripts/check",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\n./scripts/sync-version\nuv run ruff format --check --diff $SOURCE_FILES\nuv run mypy $SOURCE_FILES\nuv run ruff check $SOURCE_FILES\n"
  },
  {
    "path": "scripts/coverage",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\nuv run coverage combine\nuv run coverage report\n"
  },
  {
    "path": "scripts/docs",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv run mkdocs \"$@\"\n"
  },
  {
    "path": "scripts/install",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv sync --frozen\n"
  },
  {
    "path": "scripts/lint",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\nuv run ruff format $SOURCE_FILES\nuv run ruff check --fix $SOURCE_FILES\n"
  },
  {
    "path": "scripts/sync-version",
    "content": "#!/bin/sh -e\n\nSEMVER_REGEX=\"([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*))?(\\+[0-9A-Za-z-]+)?\"\nCHANGELOG_VERSION=$(grep -o -E $SEMVER_REGEX docs/release-notes.md | head -1)\nVERSION=$(grep -o -E $SEMVER_REGEX uvicorn/__init__.py | head -1)\nif [ \"$CHANGELOG_VERSION\" != \"$VERSION\" ]; then\n    echo \"Version in changelog does not match version in uvicorn/__init__.py!\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/check\nfi\n\nexport COVERAGE_PROCESS_START=$(pwd)/pyproject.toml\n\nuv run coverage run --debug config -m pytest \"$@\"\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/coverage\nfi\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/benchmarks/__init__.py",
    "content": ""
  },
  {
    "path": "tests/benchmarks/http.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nfrom uvicorn._types import ASGIApplication, Scope\nfrom uvicorn.config import Config\nfrom uvicorn.lifespan.off import LifespanOff\nfrom uvicorn.protocols.http.h11_impl import H11Protocol\nfrom uvicorn.server import ServerState\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol\n\n    WSProtocol: TypeAlias = WebSocketProtocol | _WSProtocol\n    HTTPProtocol: TypeAlias = H11Protocol | HttpToolsProtocol\n\n\nSIMPLE_GET_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.1\", b\"Host: example.org\", b\"\", b\"\"])\n\nSIMPLE_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: application/json\",\n        b\"Content-Length: 18\",\n        b\"\",\n        b'{\"hello\": \"world\"}',\n    ]\n)\n\nLARGE_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: text/plain\",\n        b\"Content-Length: 100000\",\n        b\"\",\n        b\"x\" * 100000,\n    ]\n)\n\nHTTP10_GET_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.0\", b\"Host: example.org\", b\"\", b\"\"])\n\nCONNECTION_CLOSE_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.1\", b\"Host: example.org\", b\"Connection: close\", b\"\", b\"\"])\n\nSTART_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: application/json\",\n        b\"Content-Length: 18\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nFINISH_POST_REQUEST = b'{\"hello\": \"world\"}'\n\nBODY_CHUNK_SIZE = 256\nFRAGMENTED_BODY_SIZE = 100_000\nFRAGMENTED_POST_HEADERS = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: application/octet-stream\",\n        b\"Content-Length: \" + str(FRAGMENTED_BODY_SIZE).encode(),\n        b\"\",\n        b\"\",\n    ]\n)\nFRAGMENTED_BODY_CHUNKS = [b\"x\" * BODY_CHUNK_SIZE] * (FRAGMENTED_BODY_SIZE // BODY_CHUNK_SIZE)\n\n\nclass MockTransport:\n    def __init__(self) -> None:\n        self.buffer = b\"\"\n        self.closed = False\n        self.read_paused = False\n\n    def get_extra_info(self, key: Any) -> Any:\n        return {\n            \"sockname\": (\"127.0.0.1\", 8000),\n            \"peername\": (\"127.0.0.1\", 8001),\n            \"sslcontext\": False,\n        }.get(key)\n\n    def write(self, data: bytes) -> None:\n        self.buffer += data\n\n    def close(self) -> None:\n        self.closed = True\n\n    def pause_reading(self) -> None:\n        self.read_paused = True\n\n    def resume_reading(self) -> None:\n        self.read_paused = False\n\n    def is_closing(self) -> bool:\n        return self.closed\n\n    def clear_buffer(self) -> None:\n        self.buffer = b\"\"\n\n    def set_protocol(self, protocol: asyncio.Protocol) -> None:\n        pass\n\n\nclass MockTimerHandle:\n    def __init__(\n        self, loop_later_list: list[MockTimerHandle], delay: float, callback: Callable[[], None], args: tuple[Any, ...]\n    ) -> None:\n        self.loop_later_list = loop_later_list\n        self.delay = delay\n        self.callback = callback\n        self.args = args\n        self.cancelled = False\n\n    def cancel(self) -> None:\n        if not self.cancelled:\n            self.cancelled = True\n            self.loop_later_list.remove(self)\n\n\nclass MockLoop:\n    def __init__(self) -> None:\n        self._tasks: list[asyncio.Task[Any]] = []\n        self._later: list[MockTimerHandle] = []\n\n    def create_task(self, coroutine: Any) -> Any:\n        self._tasks.insert(0, coroutine)\n        return MockTask()\n\n    def call_later(self, delay: float, callback: Callable[[], None], *args: Any) -> MockTimerHandle:\n        handle = MockTimerHandle(self._later, delay, callback, args)\n        self._later.insert(0, handle)\n        return handle\n\n    async def run_one(self) -> Any:\n        return await self._tasks.pop()\n\n\nclass MockTask:\n    def add_done_callback(self, callback: Callable[[], None]) -> None:\n        pass\n\n\nclass MockProtocol(asyncio.Protocol):\n    loop: MockLoop\n    transport: MockTransport\n    timeout_keep_alive_task: asyncio.TimerHandle | None\n    ws_protocol_class: type[WSProtocol] | None\n    scope: Scope\n\n\ndef make_config(app: ASGIApplication, **kwargs: Any) -> Config:\n    return Config(app=app, **kwargs)\n\n\ndef get_connected_protocol(\n    config: Config,\n    http_protocol_cls: type[HTTPProtocol],\n) -> MockProtocol:\n    loop = MockLoop()\n    transport = MockTransport()\n    lifespan = LifespanOff(config)\n    server_state = ServerState()\n    protocol = http_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop)  # type: ignore\n    protocol.connection_made(transport)  # type: ignore[arg-type]\n    return protocol  # type: ignore[return-value]\n"
  },
  {
    "path": "tests/benchmarks/test_http.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport pytest\n\nfrom tests.benchmarks.http import (\n    CONNECTION_CLOSE_REQUEST,\n    FINISH_POST_REQUEST,\n    FRAGMENTED_BODY_CHUNKS,\n    FRAGMENTED_POST_HEADERS,\n    HTTP10_GET_REQUEST,\n    LARGE_POST_REQUEST,\n    SIMPLE_GET_REQUEST,\n    SIMPLE_POST_REQUEST,\n    START_POST_REQUEST,\n    get_connected_protocol,\n    make_config,\n)\nfrom tests.response import Response\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\nif TYPE_CHECKING:\n    from tests.benchmarks.http import HTTPProtocol\n\npytestmark = [pytest.mark.anyio, pytest.mark.benchmark]\n\n_plain_text_app = Response(\"Hello, world\", media_type=\"text/plain\")\n_no_content_app = Response(b\"\", status_code=204)\n_chunked_app = Response(b\"Hello, world!\", status_code=200, headers={\"transfer-encoding\": \"chunked\"})\n\n_plain_text_config = make_config(_plain_text_app)\n_chunked_config = make_config(_chunked_app)\n\n\nasync def _body_echo_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    body = b\"\"\n    while True:\n        message = await receive()\n        body += message.get(\"body\", b\"\")  # type: ignore[operator]\n        if not message.get(\"more_body\", False):\n            break\n    headers = [(b\"content-length\", str(len(body)).encode())]\n    await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": body})\n\n\n_body_echo_config = make_config(_body_echo_app)\n\n\nasync def test_bench_simple_get(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_simple_post(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(SIMPLE_POST_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_large_post(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(LARGE_POST_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_pipelined_requests(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST * 3)\n    await protocol.loop.run_one()\n    await protocol.loop.run_one()\n    await protocol.loop.run_one()\n\n\nasync def test_bench_keepalive_reuse(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_chunked_response(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_chunked_config, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_http10(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(HTTP10_GET_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_connection_close(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(CONNECTION_CLOSE_REQUEST)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_fragmented_body(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)\n    protocol.data_received(FRAGMENTED_POST_HEADERS)\n    for chunk in FRAGMENTED_BODY_CHUNKS:\n        protocol.data_received(chunk)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_post_body_receive(http_protocol_cls: type[HTTPProtocol]) -> None:\n    protocol = get_connected_protocol(_body_echo_config, http_protocol_cls)\n    protocol.data_received(START_POST_REQUEST)\n    protocol.data_received(FINISH_POST_REQUEST)\n    await protocol.loop.run_one()\n"
  },
  {
    "path": "tests/benchmarks/test_ws.py",
    "content": "from __future__ import annotations\n\nimport importlib.util\nfrom typing import TYPE_CHECKING\n\nimport pytest\n\nfrom tests.benchmarks.http import make_config\nfrom tests.benchmarks.ws import WS_UPGRADE, get_connected_ws_protocol\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\nif TYPE_CHECKING:\n    from tests.benchmarks.ws import WSProtocolClass\n\npytestmark = [pytest.mark.anyio, pytest.mark.benchmark]\n\n\n@pytest.fixture(\n    params=[\n        pytest.param(\n            \"wsproto\",\n            marks=pytest.mark.skipif(not importlib.util.find_spec(\"wsproto\"), reason=\"wsproto not installed.\"),\n            id=\"wsproto\",\n        ),\n        pytest.param(\"websockets-sansio\", id=\"websockets-sansio\"),\n    ]\n)\ndef ws_cls(request: pytest.FixtureRequest) -> WSProtocolClass:\n    if request.param == \"wsproto\":\n        from uvicorn.protocols.websockets.wsproto_impl import WSProtocol\n\n        return WSProtocol\n    from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol\n\n    return WebSocketsSansIOProtocol\n\n\nasync def _ws_accept_close_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    await receive()\n    await send({\"type\": \"websocket.accept\"})\n    await send({\"type\": \"websocket.close\", \"code\": 1000})\n\n\nasync def _ws_send_text_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    await receive()\n    await send({\"type\": \"websocket.accept\"})\n    await send({\"type\": \"websocket.send\", \"text\": \"Hello, world!\"})\n    await send({\"type\": \"websocket.close\", \"code\": 1000})\n\n\n_ws_accept_close_config = make_config(_ws_accept_close_app, access_log=False)\n_ws_send_text_config = make_config(_ws_send_text_app, access_log=False)\n\n\nasync def test_bench_ws_handshake(ws_cls: WSProtocolClass) -> None:\n    protocol = get_connected_ws_protocol(_ws_accept_close_config, ws_cls)\n    protocol.data_received(WS_UPGRADE)\n    await protocol.loop.run_one()\n\n\nasync def test_bench_ws_send_text(ws_cls: WSProtocolClass) -> None:\n    protocol = get_connected_ws_protocol(_ws_send_text_config, ws_cls)\n    protocol.data_received(WS_UPGRADE)\n    await protocol.loop.run_one()\n"
  },
  {
    "path": "tests/benchmarks/ws.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nfrom tests.benchmarks.http import MockLoop, MockTransport\nfrom uvicorn.config import Config\nfrom uvicorn.lifespan.off import LifespanOff\nfrom uvicorn.server import ServerState\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol\n\n    WSProtocolClass: TypeAlias = type[WSProtocol] | type[WebSocketsSansIOProtocol]\n\nWS_UPGRADE = (\n    b\"GET / HTTP/1.1\\r\\n\"\n    b\"Host: example.org\\r\\n\"\n    b\"Upgrade: websocket\\r\\n\"\n    b\"Connection: Upgrade\\r\\n\"\n    b\"Sec-WebSocket-Key: YmVuY2htYXJra2V5MTIzNA==\\r\\n\"\n    b\"Sec-WebSocket-Version: 13\\r\\n\"\n    b\"\\r\\n\"\n)\n\n# Masked text frame: \"Hello, world!\" (13 bytes) with zero mask key\nWS_TEXT_FRAME = b\"\\x81\\x8d\\x00\\x00\\x00\\x00Hello, world!\"\n\n# Masked close frame: code 1000 with zero mask key\nWS_CLOSE_FRAME = b\"\\x88\\x82\\x00\\x00\\x00\\x00\\x03\\xe8\"\n\n\ndef get_connected_ws_protocol(config: Config, ws_protocol_cls: WSProtocolClass) -> Any:\n    loop = MockLoop()\n    transport = MockTransport()\n    lifespan = LifespanOff(config)\n    server_state = ServerState()\n    protocol = ws_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop)  # type: ignore[arg-type]\n    protocol.connection_made(transport)  # type: ignore[arg-type]\n    return protocol\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport importlib.util\nimport os\nimport socket\nimport ssl\nfrom copy import deepcopy\nfrom hashlib import md5\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom typing import Any\nfrom uuid import uuid4\n\nimport pytest\n\ntry:\n    import trustme\n    from cryptography.hazmat.backends import default_backend\n    from cryptography.hazmat.primitives import serialization\n\n    HAVE_TRUSTME = True\nexcept ImportError:  # pragma: no cover\n    HAVE_TRUSTME = False\n\nfrom uvicorn.config import LOGGING_CONFIG\nfrom uvicorn.importer import import_from_string\n\n# Note: We explicitly turn the propagate on just for tests, because pytest\n# caplog not able to capture no-propagate loggers.\n#\n# And the caplog_for_logger helper also not work on test config cases, because\n# when create Config object, Config.configure_logging will remove caplog.handler.\n#\n# The simple solution is set propagate=True before execute tests.\n#\n# See also: https://github.com/pytest-dev/pytest/issues/3697\nLOGGING_CONFIG[\"loggers\"][\"uvicorn\"][\"propagate\"] = True\n\n\n@pytest.fixture\ndef tls_certificate_authority() -> trustme.CA:\n    if not HAVE_TRUSTME:\n        pytest.skip(\"trustme not installed\")  # pragma: no cover\n    return trustme.CA()\n\n\n@pytest.fixture\ndef tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.LeafCert:\n    return tls_certificate_authority.issue_cert(\n        \"localhost\",\n        \"127.0.0.1\",\n        \"::1\",\n    )\n\n\n@pytest.fixture\ndef tls_ca_certificate_pem_path(tls_certificate_authority: trustme.CA):\n    with tls_certificate_authority.cert_pem.tempfile() as ca_cert_pem:\n        yield ca_cert_pem\n\n\n@pytest.fixture\ndef tls_ca_certificate_private_key_path(tls_certificate_authority: trustme.CA):\n    with tls_certificate_authority.private_key_pem.tempfile() as private_key:\n        yield private_key\n\n\n@pytest.fixture\ndef tls_certificate_private_key_encrypted_path(tls_certificate):\n    private_key = serialization.load_pem_private_key(\n        tls_certificate.private_key_pem.bytes(),\n        password=None,\n        backend=default_backend(),\n    )\n    encrypted_key = private_key.private_bytes(\n        serialization.Encoding.PEM,\n        serialization.PrivateFormat.TraditionalOpenSSL,\n        serialization.BestAvailableEncryption(b\"uvicorn password for the win\"),\n    )\n    with trustme.Blob(encrypted_key).tempfile() as private_encrypted_key:\n        yield private_encrypted_key\n\n\n@pytest.fixture\ndef tls_certificate_private_key_path(tls_certificate: trustme.CA):\n    with tls_certificate.private_key_pem.tempfile() as private_key:\n        yield private_key\n\n\n@pytest.fixture\ndef tls_certificate_key_and_chain_path(tls_certificate: trustme.LeafCert):\n    with tls_certificate.private_key_and_cert_chain_pem.tempfile() as cert_pem:\n        yield cert_pem\n\n\n@pytest.fixture\ndef tls_certificate_server_cert_path(tls_certificate: trustme.LeafCert):\n    with tls_certificate.cert_chain_pems[0].tempfile() as cert_pem:\n        yield cert_pem\n\n\n@pytest.fixture\ndef tls_ca_ssl_context(tls_certificate_authority: trustme.CA) -> ssl.SSLContext:\n    ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)\n    tls_certificate_authority.configure_trust(ssl_ctx)\n    return ssl_ctx\n\n\n@pytest.fixture(scope=\"package\")\ndef reload_directory_structure(tmp_path_factory: pytest.TempPathFactory):\n    \"\"\"\n    This fixture creates a directory structure to enable reload parameter tests\n\n    The fixture has the following structure:\n    root\n    ├── [app, app_first, app_second, app_third]\n    │   ├── css\n    │   │   └── main.css\n    │   ├── js\n    │   │   └── main.js\n    │   ├── src\n    │   │   └── main.py\n    │   └── sub\n    │       └── sub.py\n    ├── ext\n    │   └── ext.jpg\n    ├── .dotted\n    ├── .dotted_dir\n    │   └── file.txt\n    └── main.py\n    \"\"\"\n    root = tmp_path_factory.mktemp(\"reload_directory\")\n    apps = [\"app\", \"app_first\", \"app_second\", \"app_third\"]\n\n    root_file = root / \"main.py\"\n    root_file.touch()\n\n    dotted_file = root / \".dotted\"\n    dotted_file.touch()\n\n    dotted_dir = root / \".dotted_dir\"\n    dotted_dir.mkdir()\n    dotted_dir_file = dotted_dir / \"file.txt\"\n    dotted_dir_file.touch()\n\n    for app in apps:\n        app_path = root / app\n        app_path.mkdir()\n        dir_files = [\n            (\"src\", [\"main.py\"]),\n            (\"js\", [\"main.js\"]),\n            (\"css\", [\"main.css\"]),\n            (\"sub\", [\"sub.py\"]),\n        ]\n        for directory, files in dir_files:\n            directory_path = app_path / directory\n            directory_path.mkdir()\n            for file in files:\n                file_path = directory_path / file\n                file_path.touch()\n    ext_dir = root / \"ext\"\n    ext_dir.mkdir()\n    ext_file = ext_dir / \"ext.jpg\"\n    ext_file.touch()\n\n    yield root\n\n\n@pytest.fixture\ndef anyio_backend() -> str:\n    return \"asyncio\"\n\n\n@pytest.fixture(scope=\"function\")\ndef logging_config() -> dict[str, Any]:\n    return deepcopy(LOGGING_CONFIG)\n\n\n@pytest.fixture\ndef short_socket_name(tmp_path, tmp_path_factory):  # pragma: py-win32\n    max_sock_len = 100\n    socket_filename = \"my.sock\"\n    identifier = f\"{uuid4()}-\"\n    identifier_len = len(identifier.encode())\n    tmp_dir = Path(\"/tmp\").resolve()\n    os_tmp_dir = Path(os.getenv(\"TMPDIR\", \"/tmp\")).resolve()\n    basetemp = Path(\n        str(tmp_path_factory.getbasetemp()),\n    ).resolve()\n    hash_basetemp = md5(\n        str(basetemp).encode(),\n    ).hexdigest()\n\n    def make_tmp_dir(base_dir):\n        return TemporaryDirectory(\n            dir=str(base_dir),\n            prefix=\"p-\",\n            suffix=f\"-{hash_basetemp}\",\n        )\n\n    paths = basetemp, os_tmp_dir, tmp_dir\n    for _num, tmp_dir_path in enumerate(paths, 1):\n        with make_tmp_dir(tmp_dir_path) as tmpd:\n            tmpd = Path(tmpd).resolve()\n            sock_path = str(tmpd / socket_filename)\n            sock_path_len = len(sock_path.encode())\n            if sock_path_len <= max_sock_len:\n                if max_sock_len - sock_path_len >= identifier_len:  # pragma: no cover\n                    sock_path = str(tmpd / \"\".join((identifier, socket_filename)))\n                yield sock_path\n                return\n\n\ndef _unused_port(socket_type: int) -> int:\n    \"\"\"Find an unused localhost port from 1024-65535 and return it.\"\"\"\n    with contextlib.closing(socket.socket(type=socket_type)) as sock:\n        sock.bind((\"127.0.0.1\", 0))\n        return sock.getsockname()[1]\n\n\n# This was copied from pytest-asyncio.\n# Ref.: https://github.com/pytest-dev/pytest-asyncio/blob/25d9592286682bc6dbfbf291028ff7a9594cf283/pytest_asyncio/plugin.py#L525-L527  # noqa: E501\n@pytest.fixture\ndef unused_tcp_port() -> int:\n    return _unused_port(socket.SOCK_STREAM)\n\n\n@pytest.fixture(\n    params=[\n        pytest.param(\n            \"uvicorn.protocols.websockets.wsproto_impl:WSProtocol\",\n            marks=pytest.mark.skipif(not importlib.util.find_spec(\"wsproto\"), reason=\"wsproto not installed.\"),\n            id=\"wsproto\",\n        ),\n        pytest.param(\"uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol\", id=\"websockets\"),\n        pytest.param(\n            \"uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol\", id=\"websockets-sansio\"\n        ),\n    ]\n)\ndef ws_protocol_cls(request: pytest.FixtureRequest):\n    return import_from_string(request.param)\n\n\n@pytest.fixture(\n    params=[\n        pytest.param(\n            \"uvicorn.protocols.http.httptools_impl:HttpToolsProtocol\",\n            marks=pytest.mark.skipif(\n                not importlib.util.find_spec(\"httptools\"),\n                reason=\"httptools not installed.\",\n            ),\n            id=\"httptools\",\n        ),\n        pytest.param(\"uvicorn.protocols.http.h11_impl:H11Protocol\", id=\"h11\"),\n    ]\n)\ndef http_protocol_cls(request: pytest.FixtureRequest):\n    return import_from_string(request.param)\n"
  },
  {
    "path": "tests/custom_loop_utils.py",
    "content": "from __future__ import annotations\n\nimport asyncio\n\n\nclass CustomLoop(asyncio.SelectorEventLoop):\n    pass\n"
  },
  {
    "path": "tests/importer/__init__.py",
    "content": ""
  },
  {
    "path": "tests/importer/circular_import_a.py",
    "content": "# Used by test_importer.py\nfrom .circular_import_b import foo  # noqa\n\nbar = 123  # pragma: no cover\n"
  },
  {
    "path": "tests/importer/circular_import_b.py",
    "content": "# Used by test_importer.py\nfrom .circular_import_a import bar  # noqa\n\nfoo = 123  # pragma: no cover\n"
  },
  {
    "path": "tests/importer/raise_import_error.py",
    "content": "# Used by test_importer.py\n\nmyattr = 123\n\nimport does_not_exist  # noqa\n"
  },
  {
    "path": "tests/importer/test_importer.py",
    "content": "import pytest\n\nfrom uvicorn.importer import ImportFromStringError, import_from_string\n\n\ndef test_invalid_format() -> None:\n    with pytest.raises(ImportFromStringError) as exc_info:\n        import_from_string(\"example:\")\n    expected = 'Import string \"example:\" must be in format \"<module>:<attribute>\".'\n    assert expected in str(exc_info.value)\n\n\ndef test_invalid_module() -> None:\n    with pytest.raises(ImportFromStringError) as exc_info:\n        import_from_string(\"module_does_not_exist:myattr\")\n    expected = 'Could not import module \"module_does_not_exist\".'\n    assert expected in str(exc_info.value)\n\n\ndef test_invalid_attr() -> None:\n    with pytest.raises(ImportFromStringError) as exc_info:\n        import_from_string(\"tempfile:attr_does_not_exist\")\n    expected = 'Attribute \"attr_does_not_exist\" not found in module \"tempfile\".'\n    assert expected in str(exc_info.value)\n\n\ndef test_internal_import_error() -> None:\n    with pytest.raises(ImportError):\n        import_from_string(\"tests.importer.raise_import_error:myattr\")\n\n\ndef test_valid_import() -> None:\n    instance = import_from_string(\"tempfile:TemporaryFile\")\n    from tempfile import TemporaryFile\n\n    assert instance == TemporaryFile\n\n\ndef test_no_import_needed() -> None:\n    from tempfile import TemporaryFile\n\n    instance = import_from_string(TemporaryFile)\n    assert instance == TemporaryFile\n\n\ndef test_circular_import_error() -> None:\n    with pytest.raises(ImportError) as exc_info:\n        import_from_string(\"tests.importer.circular_import_a:bar\")\n    expected = (\n        \"cannot import name 'bar' from partially initialized module \"\n        \"'tests.importer.circular_import_a' (most likely due to a circular import)\"\n    )\n    assert expected in str(exc_info.value)\n"
  },
  {
    "path": "tests/middleware/__init__.py",
    "content": ""
  },
  {
    "path": "tests/middleware/test_logging.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport logging\nimport socket\nimport sys\nfrom collections.abc import Iterator\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nimport httpx\nimport pytest\nimport websockets.client\nfrom websockets.protocol import State\n\nfrom tests.utils import run_server\nfrom uvicorn import Config\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\nif TYPE_CHECKING:\n    import sys\n\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol\n\n    WSProtocol: TypeAlias = \"type[WebSocketProtocol | _WSProtocol]\"\n\npytestmark = pytest.mark.anyio\n\n\n@contextlib.contextmanager\ndef caplog_for_logger(caplog: pytest.LogCaptureFixture, logger_name: str) -> Iterator[pytest.LogCaptureFixture]:\n    logger = logging.getLogger(logger_name)\n    logger.propagate, old_propagate = False, logger.propagate\n    logger.addHandler(caplog.handler)\n    try:\n        yield caplog\n    finally:\n        logger.removeHandler(caplog.handler)\n        logger.propagate = old_propagate\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n    assert scope[\"type\"] == \"http\"\n    await send({\"type\": \"http.response.start\", \"status\": 204, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n\nasync def test_trace_logging(caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int):\n    config = Config(\n        app=app,\n        log_level=\"trace\",\n        log_config=logging_config,\n        lifespan=\"auto\",\n        port=unused_tcp_port,\n    )\n    with caplog_for_logger(caplog, \"uvicorn.asgi\"):\n        async with run_server(config):\n            async with httpx.AsyncClient() as client:\n                response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n        assert response.status_code == 204\n        messages = [record.message for record in caplog.records if record.name == \"uvicorn.asgi\"]\n        assert \"ASGI [1] Started scope=\" in messages.pop(0)\n        assert \"ASGI [1] Raised exception\" in messages.pop(0)\n        assert \"ASGI [2] Started scope=\" in messages.pop(0)\n        assert \"ASGI [2] Send \" in messages.pop(0)\n        assert \"ASGI [2] Send \" in messages.pop(0)\n        assert \"ASGI [2] Completed\" in messages.pop(0)\n\n\nasync def test_trace_logging_on_http_protocol(http_protocol_cls, caplog, logging_config, unused_tcp_port: int):\n    config = Config(\n        app=app,\n        log_level=\"trace\",\n        http=http_protocol_cls,\n        log_config=logging_config,\n        port=unused_tcp_port,\n    )\n    with caplog_for_logger(caplog, \"uvicorn.error\"):\n        async with run_server(config):\n            async with httpx.AsyncClient() as client:\n                response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n        assert response.status_code == 204\n        messages = [record.message for record in caplog.records if record.name == \"uvicorn.error\"]\n        assert any(\" - HTTP connection made\" in message for message in messages)\n        assert any(\" - HTTP connection lost\" in message for message in messages)\n\n\nasync def test_trace_logging_on_ws_protocol(\n    ws_protocol_cls: WSProtocol,\n    caplog: pytest.LogCaptureFixture,\n    logging_config: dict[str, Any],\n    unused_tcp_port: int,\n):\n    async def websocket_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"websocket\"\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"websocket.connect\":\n                await send({\"type\": \"websocket.accept\"})\n            elif message[\"type\"] == \"websocket.disconnect\":\n                break\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.state is State.OPEN\n\n    config = Config(\n        app=websocket_app,\n        log_level=\"trace\",\n        log_config=logging_config,\n        ws=ws_protocol_cls,\n        port=unused_tcp_port,\n    )\n    with caplog_for_logger(caplog, \"uvicorn.error\"):\n        async with run_server(config):\n            is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert is_open\n        messages = [record.message for record in caplog.records if record.name == \"uvicorn.error\"]\n        assert any(\" - Upgrading to WebSocket\" in message for message in messages)\n        assert any(\" - WebSocket connection made\" in message for message in messages)\n        assert any(\" - WebSocket connection lost\" in message for message in messages)\n\n\n@pytest.mark.parametrize(\"use_colors\", [(True), (False), (None)])\nasync def test_access_logging(\n    use_colors: bool, caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int\n):\n    config = Config(app=app, use_colors=use_colors, log_config=logging_config, port=unused_tcp_port)\n    with caplog_for_logger(caplog, \"uvicorn.access\"):\n        async with run_server(config):\n            async with httpx.AsyncClient() as client:\n                response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n\n        assert response.status_code == 204\n        messages = [record.message for record in caplog.records if record.name == \"uvicorn.access\"]\n        assert '\"GET / HTTP/1.1\" 204' in messages.pop()\n\n\n@pytest.mark.parametrize(\"use_colors\", [(True), (False)])\nasync def test_default_logging(\n    use_colors: bool, caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int\n):\n    config = Config(app=app, use_colors=use_colors, log_config=logging_config, port=unused_tcp_port)\n    with caplog_for_logger(caplog, \"uvicorn.access\"):\n        async with run_server(config):\n            async with httpx.AsyncClient() as client:\n                response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n        assert response.status_code == 204\n        messages = [record.message for record in caplog.records if \"uvicorn\" in record.name]\n        assert \"Started server process\" in messages.pop(0)\n        assert \"Waiting for application startup\" in messages.pop(0)\n        assert \"ASGI 'lifespan' protocol appears unsupported\" in messages.pop(0)\n        assert \"Application startup complete\" in messages.pop(0)\n        assert \"Uvicorn running on http://127.0.0.1\" in messages.pop(0)\n        assert '\"GET / HTTP/1.1\" 204' in messages.pop(0)\n        assert \"Shutting down\" in messages.pop(0)\n\n\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"require unix-like system\")\nasync def test_running_log_using_uds(\n    caplog: pytest.LogCaptureFixture, short_socket_name: str, unused_tcp_port: int\n):  # pragma: py-win32\n    config = Config(app=app, uds=short_socket_name, port=unused_tcp_port)\n    with caplog_for_logger(caplog, \"uvicorn.access\"):\n        async with run_server(config):\n            ...\n\n    messages = [record.message for record in caplog.records if \"uvicorn\" in record.name]\n    assert f\"Uvicorn running on unix socket {short_socket_name} (Press CTRL+C to quit)\" in messages\n\n\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"require unix-like system\")\nasync def test_running_log_using_fd(caplog: pytest.LogCaptureFixture, unused_tcp_port: int):  # pragma: py-win32\n    with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:\n        fd = sock.fileno()\n        config = Config(app=app, fd=fd, port=unused_tcp_port)\n        with caplog_for_logger(caplog, \"uvicorn.access\"):\n            async with run_server(config):\n                ...\n        sockname = sock.getsockname()\n    messages = [record.message for record in caplog.records if \"uvicorn\" in record.name]\n    assert f\"Uvicorn running on socket {sockname} (Press CTRL+C to quit)\" in messages\n\n\nasync def test_unknown_status_code(caplog: pytest.LogCaptureFixture, unused_tcp_port: int):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"http\"\n        await send({\"type\": \"http.response.start\", \"status\": 599, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n    config = Config(app=app, port=unused_tcp_port)\n    with caplog_for_logger(caplog, \"uvicorn.access\"):\n        async with run_server(config):\n            async with httpx.AsyncClient() as client:\n                response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n\n        assert response.status_code == 599\n        messages = [record.message for record in caplog.records if record.name == \"uvicorn.access\"]\n        assert '\"GET / HTTP/1.1\" 599' in messages.pop()\n\n\nasync def test_server_start_with_port_zero(caplog: pytest.LogCaptureFixture):\n    config = Config(app=app, port=0)\n    async with run_server(config) as _server:\n        server = _server.servers[0]\n        sock = server.sockets[0]\n        host, port = sock.getsockname()\n    messages = [record.message for record in caplog.records if \"uvicorn\" in record.name]\n    assert f\"Uvicorn running on http://{host}:{port} (Press CTRL+C to quit)\" in messages\n"
  },
  {
    "path": "tests/middleware/test_message_logger.py",
    "content": "import httpx\nimport pytest\n\nfrom tests.middleware.test_logging import caplog_for_logger\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.middleware.message_logger import MessageLoggerMiddleware\n\n\n@pytest.mark.anyio\nasync def test_message_logger(caplog: pytest.LogCaptureFixture) -> None:\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        await receive()\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n    with caplog_for_logger(caplog, \"uvicorn.asgi\"):\n        caplog.set_level(TRACE_LOG_LEVEL, logger=\"uvicorn.asgi\")\n        caplog.set_level(TRACE_LOG_LEVEL)\n\n        transport = httpx.ASGITransport(MessageLoggerMiddleware(app))  # type: ignore\n        async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n            response = await client.get(\"/\")\n        assert response.status_code == 200\n        messages = [record.msg % record.args for record in caplog.records]\n        assert sum([\"ASGI [1] Started\" in message for message in messages]) == 1\n        assert sum([\"ASGI [1] Send\" in message for message in messages]) == 2\n        assert sum([\"ASGI [1] Receive\" in message for message in messages]) == 1\n        assert sum([\"ASGI [1] Completed\" in message for message in messages]) == 1\n        assert sum([\"ASGI [1] Raised exception\" in message for message in messages]) == 0\n\n\n@pytest.mark.anyio\nasync def test_message_logger_exc(caplog: pytest.LogCaptureFixture) -> None:\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        raise RuntimeError()\n\n    with caplog_for_logger(caplog, \"uvicorn.asgi\"):\n        caplog.set_level(TRACE_LOG_LEVEL, logger=\"uvicorn.asgi\")\n        caplog.set_level(TRACE_LOG_LEVEL)\n        transport = httpx.ASGITransport(MessageLoggerMiddleware(app))  # type: ignore\n        async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n            with pytest.raises(RuntimeError):\n                await client.get(\"/\")\n        messages = [record.msg % record.args for record in caplog.records]\n        assert sum([\"ASGI [1] Started\" in message for message in messages]) == 1\n        assert sum([\"ASGI [1] Send\" in message for message in messages]) == 0\n        assert sum([\"ASGI [1] Receive\" in message for message in messages]) == 0\n        assert sum([\"ASGI [1] Completed\" in message for message in messages]) == 0\n        assert sum([\"ASGI [1] Raised exception\" in message for message in messages]) == 1\n"
  },
  {
    "path": "tests/middleware/test_proxy_headers.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport httpx\nimport httpx._transports.asgi\nimport pytest\nimport websockets.client\n\nfrom tests.response import Response\nfrom tests.utils import run_server\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.config import Config\nfrom uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware, _TrustedHosts\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.http.h11_impl import H11Protocol\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol\n\n\nX_FORWARDED_FOR = \"X-Forwarded-For\"\nX_FORWARDED_PROTO = \"X-Forwarded-Proto\"\n\n\nasync def default_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    scheme = scope[\"scheme\"]  # type: ignore\n    if (client := scope[\"client\"]) is None:  # type: ignore\n        client_addr = \"NONE\"  # pragma: no cover\n    else:\n        host, port = client\n        client_addr = f\"{host}:{port}\"\n\n    response = Response(f\"{scheme}://{client_addr}\", media_type=\"text/plain\")\n    await response(scope, receive, send)\n\n\ndef make_httpx_client(\n    trusted_hosts: str | list[str],\n    client: tuple[str, int] = (\"127.0.0.1\", 123),\n) -> httpx.AsyncClient:\n    \"\"\"Create async client for use in test cases.\n\n    Args:\n        trusted_hosts: trusted_hosts for proxy middleware\n        client: transport client to use\n    \"\"\"\n\n    app = ProxyHeadersMiddleware(default_app, trusted_hosts)\n    transport = httpx.ASGITransport(app=app, client=client)  # type: ignore\n    return httpx.AsyncClient(transport=transport, base_url=\"http://testserver\")\n\n\n# Note: we vary the format here to also test some of the functionality\n# of the _TrustedHosts.__init__ method.\n_TRUSTED_NOTHING: list[str] = []\n_TRUSTED_EVERYTHING = \"*\"\n_TRUSTED_EVERYTHING_LIST = [\"*\"]\n_TRUSTED_IPv4_ADDRESSES = \"127.0.0.1, 10.0.0.1\"\n_TRUSTED_IPv4_NETWORKS = [\"127.0.0.0/8\", \"10.0.0.0/8\"]\n_TRUSTED_IPv6_ADDRESSES = [\n    \"2001:db8::\",\n    \"2001:0db8:0001:0000:0000:0ab9:C0A8:0102\",\n    \"2001:db8:3333:4444:5555:6666:1.2.3.4\",  # This is a dual address\n    \"::11.22.33.44\",  # This is a dual address\n]\n_TRUSTED_IPv6_NETWORKS = \"2001:db8:abcd:0012::0/64\"\n_TRUSTED_LITERALS = \"some-literal , unix:///foo/bar  ,  /foo/bar, garba*gewith*\"\n\n\n@pytest.mark.parametrize(\n    (\"init_hosts\", \"test_host\", \"expected\"),\n    [\n        ## Never Trust trust\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_NOTHING, \"127.0.0.0\", False),\n        (_TRUSTED_NOTHING, \"127.0.0.1\", False),\n        (_TRUSTED_NOTHING, \"127.1.1.1\", False),\n        (_TRUSTED_NOTHING, \"127.255.255.255\", False),\n        (_TRUSTED_NOTHING, \"10.0.0.0\", False),\n        (_TRUSTED_NOTHING, \"10.0.0.1\", False),\n        (_TRUSTED_NOTHING, \"10.1.1.1\", False),\n        (_TRUSTED_NOTHING, \"10.255.255.255\", False),\n        (_TRUSTED_NOTHING, \"192.168.0.0\", False),\n        (_TRUSTED_NOTHING, \"192.168.0.1\", False),\n        (_TRUSTED_NOTHING, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_NOTHING, \"2001:db8::\", False),\n        (_TRUSTED_NOTHING, \"2001:db8:abcd:0012::0\", False),\n        (_TRUSTED_NOTHING, \"2001:db8:abcd:0012::1:1\", False),\n        (_TRUSTED_NOTHING, \"::\", False),\n        (_TRUSTED_NOTHING, \"::1\", False),\n        (\n            _TRUSTED_NOTHING,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            False,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_NOTHING, \"::b16:212c\", False),  # aka ::11.22.33.44\n        (_TRUSTED_NOTHING, \"a:b:c:d::\", False),\n        (_TRUSTED_NOTHING, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_NOTHING, \"some-literal\", False),\n        (_TRUSTED_NOTHING, \"unix:///foo/bar\", False),\n        (_TRUSTED_NOTHING, \"/foo/bar\", False),\n        (_TRUSTED_NOTHING, \"*\", False),\n        (_TRUSTED_NOTHING, \"another-literal\", False),\n        (_TRUSTED_NOTHING, \"unix:///another/path\", False),\n        (_TRUSTED_NOTHING, \"/another/path\", False),\n        (_TRUSTED_NOTHING, \"\", False),\n        ## Always trust\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_EVERYTHING, \"127.0.0.0\", True),\n        (_TRUSTED_EVERYTHING, \"127.0.0.1\", True),\n        (_TRUSTED_EVERYTHING, \"127.1.1.1\", True),\n        (_TRUSTED_EVERYTHING, \"127.255.255.255\", True),\n        (_TRUSTED_EVERYTHING, \"10.0.0.0\", True),\n        (_TRUSTED_EVERYTHING, \"10.0.0.1\", True),\n        (_TRUSTED_EVERYTHING, \"10.1.1.1\", True),\n        (_TRUSTED_EVERYTHING, \"10.255.255.255\", True),\n        (_TRUSTED_EVERYTHING, \"192.168.0.0\", True),\n        (_TRUSTED_EVERYTHING, \"192.168.0.1\", True),\n        (_TRUSTED_EVERYTHING, \"1.1.1.1\", True),\n        (_TRUSTED_EVERYTHING_LIST, \"1.1.1.1\", True),\n        # Test IPv6 Addresses\n        (_TRUSTED_EVERYTHING, \"2001:db8::\", True),\n        (_TRUSTED_EVERYTHING, \"2001:db8:abcd:0012::0\", True),\n        (_TRUSTED_EVERYTHING, \"2001:db8:abcd:0012::1:1\", True),\n        (_TRUSTED_EVERYTHING, \"::\", True),\n        (_TRUSTED_EVERYTHING, \"::1\", True),\n        (\n            _TRUSTED_EVERYTHING,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            True,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_EVERYTHING, \"::b16:212c\", True),  # aka ::11.22.33.44\n        (_TRUSTED_EVERYTHING, \"a:b:c:d::\", True),\n        (_TRUSTED_EVERYTHING, \"::a:b:c:d\", True),\n        (_TRUSTED_EVERYTHING_LIST, \"::a:b:c:d\", True),\n        # Test Literals\n        (_TRUSTED_EVERYTHING, \"some-literal\", True),\n        (_TRUSTED_EVERYTHING, \"unix:///foo/bar\", True),\n        (_TRUSTED_EVERYTHING, \"/foo/bar\", True),\n        (_TRUSTED_EVERYTHING, \"*\", True),\n        (_TRUSTED_EVERYTHING, \"another-literal\", True),\n        (_TRUSTED_EVERYTHING, \"unix:///another/path\", True),\n        (_TRUSTED_EVERYTHING, \"/another/path\", True),\n        (_TRUSTED_EVERYTHING, \"\", True),\n        (_TRUSTED_EVERYTHING_LIST, \"\", True),\n        ## Trust IPv4 Addresses\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_IPv4_ADDRESSES, \"127.0.0.0\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"127.0.0.1\", True),\n        (_TRUSTED_IPv4_ADDRESSES, \"127.1.1.1\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"127.255.255.255\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"10.0.0.0\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"10.0.0.1\", True),\n        (_TRUSTED_IPv4_ADDRESSES, \"10.1.1.1\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"10.255.255.255\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"192.168.0.0\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"192.168.0.1\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_IPv4_ADDRESSES, \"2001:db8::\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"2001:db8:abcd:0012::0\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"2001:db8:abcd:0012::1:1\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"::\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"::1\", False),\n        (\n            _TRUSTED_IPv4_ADDRESSES,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            False,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_IPv4_ADDRESSES, \"::b16:212c\", False),  # aka ::11.22.33.44\n        (_TRUSTED_IPv4_ADDRESSES, \"a:b:c:d::\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_IPv4_ADDRESSES, \"some-literal\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"unix:///foo/bar\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"*\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"/foo/bar\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"another-literal\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"unix:///another/path\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"/another/path\", False),\n        (_TRUSTED_IPv4_ADDRESSES, \"\", False),\n        ## Trust IPv6 Addresses\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_IPv6_ADDRESSES, \"127.0.0.0\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"127.0.0.1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"127.1.1.1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"127.255.255.255\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"10.0.0.0\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"10.0.0.1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"10.1.1.1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"10.255.255.255\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"192.168.0.0\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"192.168.0.1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_IPv6_ADDRESSES, \"2001:db8::\", True),\n        (_TRUSTED_IPv6_ADDRESSES, \"2001:db8:abcd:0012::0\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"2001:db8:abcd:0012::1:1\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"::\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"::1\", False),\n        (\n            _TRUSTED_IPv6_ADDRESSES,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            True,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_IPv6_ADDRESSES, \"::b16:212c\", True),  # aka ::11.22.33.44\n        (_TRUSTED_IPv6_ADDRESSES, \"a:b:c:d::\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_IPv6_ADDRESSES, \"some-literal\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"unix:///foo/bar\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"*\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"/foo/bar\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"another-literal\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"unix:///another/path\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"/another/path\", False),\n        (_TRUSTED_IPv6_ADDRESSES, \"\", False),\n        ## Trust IPv4 Networks\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_IPv4_NETWORKS, \"127.0.0.0\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"127.0.0.1\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"127.1.1.1\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"127.255.255.255\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"10.0.0.0\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"10.0.0.1\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"10.1.1.1\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"10.255.255.255\", True),\n        (_TRUSTED_IPv4_NETWORKS, \"192.168.0.0\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"192.168.0.1\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_IPv4_NETWORKS, \"2001:db8::\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"2001:db8:abcd:0012::0\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"2001:db8:abcd:0012::1:1\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"::\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"::1\", False),\n        (\n            _TRUSTED_IPv4_NETWORKS,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            False,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_IPv4_NETWORKS, \"::b16:212c\", False),  # aka ::11.22.33.44\n        (_TRUSTED_IPv4_NETWORKS, \"a:b:c:d::\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_IPv4_NETWORKS, \"some-literal\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"unix:///foo/bar\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"*\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"/foo/bar\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"another-literal\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"unix:///another/path\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"/another/path\", False),\n        (_TRUSTED_IPv4_NETWORKS, \"\", False),\n        ## Trust IPv6 Networks\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_IPv6_NETWORKS, \"127.0.0.0\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"127.0.0.1\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"127.1.1.1\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"127.255.255.255\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"10.0.0.0\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"10.0.0.1\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"10.1.1.1\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"10.255.255.255\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"192.168.0.0\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"192.168.0.1\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_IPv6_NETWORKS, \"2001:db8::\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"2001:db8:abcd:0012::0\", True),\n        (_TRUSTED_IPv6_NETWORKS, \"2001:db8:abcd:0012::1:1\", True),\n        (_TRUSTED_IPv6_NETWORKS, \"::\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"::1\", False),\n        (\n            _TRUSTED_IPv6_NETWORKS,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            False,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_IPv6_NETWORKS, \"::b16:212c\", False),  # aka ::11.22.33.44\n        (_TRUSTED_IPv6_NETWORKS, \"a:b:c:d::\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_IPv6_NETWORKS, \"some-literal\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"unix:///foo/bar\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"*\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"/foo/bar\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"another-literal\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"unix:///another/path\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"/another/path\", False),\n        (_TRUSTED_IPv6_NETWORKS, \"\", False),\n        ## Trust Literals\n        ## -----------------------------\n        # Test IPv4 Addresses\n        (_TRUSTED_LITERALS, \"127.0.0.0\", False),\n        (_TRUSTED_LITERALS, \"127.0.0.1\", False),\n        (_TRUSTED_LITERALS, \"127.1.1.1\", False),\n        (_TRUSTED_LITERALS, \"127.255.255.255\", False),\n        (_TRUSTED_LITERALS, \"10.0.0.0\", False),\n        (_TRUSTED_LITERALS, \"10.0.0.1\", False),\n        (_TRUSTED_LITERALS, \"10.1.1.1\", False),\n        (_TRUSTED_LITERALS, \"10.255.255.255\", False),\n        (_TRUSTED_LITERALS, \"192.168.0.0\", False),\n        (_TRUSTED_LITERALS, \"192.168.0.1\", False),\n        (_TRUSTED_LITERALS, \"1.1.1.1\", False),\n        # Test IPv6 Addresses\n        (_TRUSTED_LITERALS, \"2001:db8::\", False),\n        (_TRUSTED_LITERALS, \"2001:db8:abcd:0012::0\", False),\n        (_TRUSTED_LITERALS, \"2001:db8:abcd:0012::1:1\", False),\n        (_TRUSTED_LITERALS, \"::\", False),\n        (_TRUSTED_LITERALS, \"::1\", False),\n        (\n            _TRUSTED_LITERALS,\n            \"2001:db8:3333:4444:5555:6666:102:304\",\n            False,\n        ),  # aka 2001:db8:3333:4444:5555:6666:1.2.3.4\n        (_TRUSTED_LITERALS, \"::b16:212c\", False),  # aka ::11.22.33.44\n        (_TRUSTED_LITERALS, \"a:b:c:d::\", False),\n        (_TRUSTED_LITERALS, \"::a:b:c:d\", False),\n        # Test Literals\n        (_TRUSTED_LITERALS, \"some-literal\", True),\n        (_TRUSTED_LITERALS, \"unix:///foo/bar\", True),\n        (_TRUSTED_LITERALS, \"*\", False),\n        (_TRUSTED_LITERALS, \"/foo/bar\", True),\n        (_TRUSTED_LITERALS, \"another-literal\", False),\n        (_TRUSTED_LITERALS, \"unix:///another/path\", False),\n        (_TRUSTED_LITERALS, \"/another/path\", False),\n        (_TRUSTED_LITERALS, \"\", False),\n    ],\n)\ndef test_forwarded_hosts(init_hosts: str | list[str], test_host: str, expected: bool) -> None:\n    trusted_hosts = _TrustedHosts(init_hosts)\n    assert (test_host in trusted_hosts) is expected\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\n    (\"trusted_hosts\", \"expected\"),\n    [\n        # always trust\n        (\"*\", \"https://1.2.3.4:0\"),\n        # trusted proxy\n        (\"127.0.0.1\", \"https://1.2.3.4:0\"),\n        ([\"127.0.0.1\"], \"https://1.2.3.4:0\"),\n        # trusted proxy list\n        ([\"127.0.0.1\", \"10.0.0.1\"], \"https://1.2.3.4:0\"),\n        (\"127.0.0.1, 10.0.0.1\", \"https://1.2.3.4:0\"),\n        # trusted proxy network\n        # https://github.com/Kludex/uvicorn/issues/1068#issuecomment-1004813267\n        (\"127.0.0.0/24, 10.0.0.1\", \"https://1.2.3.4:0\"),\n        # request from untrusted proxy\n        (\"192.168.0.1\", \"http://127.0.0.1:123\"),\n        # request from untrusted proxy network\n        (\"192.168.0.0/16\", \"http://127.0.0.1:123\"),\n        # request from client running on proxy server itself\n        # https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576\n        ([\"127.0.0.1\", \"1.2.3.4\"], \"https://1.2.3.4:0\"),\n    ],\n)\nasync def test_proxy_headers_trusted_hosts(trusted_hosts: str | list[str], expected: str) -> None:\n    async with make_httpx_client(trusted_hosts) as client:\n        headers = {X_FORWARDED_FOR: \"1.2.3.4\", X_FORWARDED_PROTO: \"https\"}\n        response = await client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == expected\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\n    (\"forwarded_for\", \"forwarded_proto\", \"expected\"),\n    [\n        (\"\", \"\", \"http://127.0.0.1:123\"),\n        (\"\", None, \"http://127.0.0.1:123\"),\n        (\"\", \"asdf\", \"http://127.0.0.1:123\"),\n        (\" , \", \"https\", \"https://127.0.0.1:123\"),\n        (\", , \", \"https\", \"https://127.0.0.1:123\"),\n        (\" , 10.0.0.1\", \"https\", \"https://127.0.0.1:123\"),\n        (\"9.9.9.9 , , , 10.0.0.1\", \"https\", \"https://127.0.0.1:123\"),\n        (\", , 9.9.9.9\", \"https\", \"https://9.9.9.9:0\"),\n        (\", , 9.9.9.9, , \", \"https\", \"https://127.0.0.1:123\"),\n    ],\n)\nasync def test_proxy_headers_trusted_hosts_malformed(\n    forwarded_for: str,\n    forwarded_proto: str | None,\n    expected: str,\n) -> None:\n    async with make_httpx_client(\"127.0.0.1, 10.0.0.0/8\") as client:\n        headers = {X_FORWARDED_FOR: forwarded_for}\n        if forwarded_proto is not None:\n            headers[X_FORWARDED_PROTO] = forwarded_proto\n        response = await client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == expected\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\n    (\"trusted_hosts\", \"expected\"),\n    [\n        # always trust\n        (\"*\", \"https://1.2.3.4:0\"),\n        # all proxies are trusted\n        ([\"127.0.0.1\", \"10.0.2.1\", \"192.168.0.2\"], \"https://1.2.3.4:0\"),\n        # order doesn't matter\n        ([\"10.0.2.1\", \"192.168.0.2\", \"127.0.0.1\"], \"https://1.2.3.4:0\"),\n        # should set first untrusted as remote address\n        ([\"192.168.0.2\", \"127.0.0.1\"], \"https://10.0.2.1:0\"),\n        # Mixed literals and networks\n        ([\"127.0.0.1\", \"10.0.0.0/8\", \"192.168.0.2\"], \"https://1.2.3.4:0\"),\n    ],\n)\nasync def test_proxy_headers_multiple_proxies(trusted_hosts: str | list[str], expected: str) -> None:\n    async with make_httpx_client(trusted_hosts) as client:\n        headers = {X_FORWARDED_FOR: \"1.2.3.4, 10.0.2.1, 192.168.0.2\", X_FORWARDED_PROTO: \"https\"}\n        response = await client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == expected\n\n\n@pytest.mark.anyio\nasync def test_proxy_headers_invalid_x_forwarded_for() -> None:\n    async with make_httpx_client(\"*\") as client:\n        headers = httpx.Headers(\n            {\n                X_FORWARDED_FOR: \"1.2.3.4, \\xf0\\xfd\\xfd\\xfd, unix:, ::1\",\n                X_FORWARDED_PROTO: \"https\",\n            },\n            encoding=\"latin-1\",\n        )\n        response = await client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"https://1.2.3.4:0\"\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\n    \"forwarded_proto,expected\",\n    [\n        (\"http\", \"ws://1.2.3.4:0\"),\n        (\"https\", \"wss://1.2.3.4:0\"),\n        (\"ws\", \"ws://1.2.3.4:0\"),\n        (\"wss\", \"wss://1.2.3.4:0\"),\n    ],\n)\nasync def test_proxy_headers_websocket_x_forwarded_proto(\n    forwarded_proto: str,\n    expected: str,\n    ws_protocol_cls: type[WSProtocol | WebSocketProtocol],\n    http_protocol_cls: type[H11Protocol | HttpToolsProtocol],\n    unused_tcp_port: int,\n) -> None:\n    async def websocket_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        assert scope[\"type\"] == \"websocket\"\n        scheme = scope[\"scheme\"]\n        assert scope[\"client\"] is not None\n        host, port = scope[\"client\"]\n        await send({\"type\": \"websocket.accept\"})\n        await send({\"type\": \"websocket.send\", \"text\": f\"{scheme}://{host}:{port}\"})\n        await send({\"type\": \"websocket.close\"})\n\n    app_with_middleware = ProxyHeadersMiddleware(websocket_app, trusted_hosts=\"*\")\n    config = Config(\n        app=app_with_middleware,\n        ws=ws_protocol_cls,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        port=unused_tcp_port,\n    )\n\n    async with run_server(config):\n        url = f\"ws://127.0.0.1:{unused_tcp_port}\"\n        headers = {X_FORWARDED_FOR: \"1.2.3.4\", X_FORWARDED_PROTO: forwarded_proto}\n        async with websockets.client.connect(url, extra_headers=headers) as websocket:\n            data = await websocket.recv()\n            assert data == expected\n\n\n@pytest.mark.anyio\nasync def test_proxy_headers_empty_x_forwarded_for() -> None:\n    # fallback to the default behavior if x-forwarded-for is an empty list\n    # https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576\n    async with make_httpx_client(\"*\") as client:\n        headers = {X_FORWARDED_FOR: \"\", X_FORWARDED_PROTO: \"https\"}\n        response = await client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"https://127.0.0.1:123\"\n"
  },
  {
    "path": "tests/middleware/test_wsgi.py",
    "content": "from __future__ import annotations\n\nimport io\nimport sys\nfrom collections.abc import AsyncGenerator, Callable\n\nimport a2wsgi\nimport httpx\nimport pytest\n\nfrom uvicorn._types import Environ, HTTPRequestEvent, HTTPScope, StartResponse\nfrom uvicorn.middleware import wsgi\n\n\ndef hello_world(environ: Environ, start_response: StartResponse) -> list[bytes]:\n    status = \"200 OK\"\n    output = b\"Hello World!\\n\"\n    headers = [\n        (\"Content-Type\", \"text/plain; charset=utf-8\"),\n        (\"Content-Length\", str(len(output))),\n    ]\n    start_response(status, headers, None)\n    return [output]\n\n\ndef echo_body(environ: Environ, start_response: StartResponse) -> list[bytes]:\n    status = \"200 OK\"\n    output = environ[\"wsgi.input\"].read()\n    headers = [\n        (\"Content-Type\", \"text/plain; charset=utf-8\"),\n        (\"Content-Length\", str(len(output))),\n    ]\n    start_response(status, headers, None)\n    return [output]\n\n\ndef raise_exception(environ: Environ, start_response: StartResponse) -> list[bytes]:\n    raise RuntimeError(\"Something went wrong\")\n\n\ndef return_exc_info(environ: Environ, start_response: StartResponse) -> list[bytes]:\n    try:\n        raise RuntimeError(\"Something went wrong\")\n    except RuntimeError:\n        status = \"500 Internal Server Error\"\n        output = b\"Internal Server Error\"\n        headers = [\n            (\"Content-Type\", \"text/plain; charset=utf-8\"),\n            (\"Content-Length\", str(len(output))),\n        ]\n        start_response(status, headers, sys.exc_info())  # type: ignore[arg-type]\n        return [output]\n\n\n@pytest.fixture(params=[wsgi._WSGIMiddleware, a2wsgi.WSGIMiddleware])\ndef wsgi_middleware(request: pytest.FixtureRequest) -> Callable:\n    return request.param\n\n\n@pytest.mark.anyio\nasync def test_wsgi_get(wsgi_middleware: Callable) -> None:\n    transport = httpx.ASGITransport(wsgi_middleware(hello_world))\n    async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        response = await client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello World!\\n\"\n\n\n@pytest.mark.anyio\nasync def test_wsgi_post(wsgi_middleware: Callable) -> None:\n    transport = httpx.ASGITransport(wsgi_middleware(echo_body))\n    async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        response = await client.post(\"/\", json={\"example\": 123})\n    assert response.status_code == 200\n    assert response.text == '{\"example\":123}'\n\n\n@pytest.mark.anyio\nasync def test_wsgi_put_more_body(wsgi_middleware: Callable) -> None:\n    async def generate_body() -> AsyncGenerator[bytes, None]:\n        for _ in range(1024):\n            yield b\"123456789abcdef\\n\" * 64\n\n    transport = httpx.ASGITransport(wsgi_middleware(echo_body))\n    async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        response = await client.put(\"/\", content=generate_body())\n    assert response.status_code == 200\n    assert response.text == \"123456789abcdef\\n\" * 64 * 1024\n\n\n@pytest.mark.anyio\nasync def test_wsgi_exception(wsgi_middleware: Callable) -> None:\n    # Note that we're testing the WSGI app directly here.\n    # The HTTP protocol implementations would catch this error and return 500.\n    transport = httpx.ASGITransport(wsgi_middleware(raise_exception))\n    async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        with pytest.raises(RuntimeError):\n            await client.get(\"/\")\n\n\n@pytest.mark.anyio\nasync def test_wsgi_exc_info(wsgi_middleware: Callable) -> None:\n    app = wsgi_middleware(return_exc_info)\n    transport = httpx.ASGITransport(\n        app=app,\n        raise_app_exceptions=False,\n    )\n    async with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        response = await client.get(\"/\")\n    assert response.status_code == 500\n    assert response.text == \"Internal Server Error\"\n\n\ndef test_build_environ_encoding() -> None:\n    scope: HTTPScope = {\n        \"asgi\": {\"version\": \"3.0\", \"spec_version\": \"2.0\"},\n        \"scheme\": \"http\",\n        \"raw_path\": b\"/\\xe6\\x96\\x87%2Fall\",\n        \"type\": \"http\",\n        \"http_version\": \"1.1\",\n        \"method\": \"GET\",\n        \"path\": \"/文/all\",\n        \"root_path\": \"/文\",\n        \"client\": None,\n        \"server\": None,\n        \"query_string\": b\"a=123&b=456\",\n        \"headers\": [(b\"key\", b\"value1\"), (b\"key\", b\"value2\")],\n        \"extensions\": {},\n    }\n    message: HTTPRequestEvent = {\n        \"type\": \"http.request\",\n        \"body\": b\"\",\n        \"more_body\": False,\n    }\n    environ = wsgi.build_environ(scope, message, io.BytesIO(b\"\"))\n    assert environ[\"SCRIPT_NAME\"] == \"/文\".encode().decode(\"latin-1\")\n    assert environ[\"PATH_INFO\"] == b\"/all\".decode(\"latin-1\")\n    assert environ[\"HTTP_KEY\"] == \"value1,value2\"\n"
  },
  {
    "path": "tests/protocols/__init__.py",
    "content": ""
  },
  {
    "path": "tests/protocols/test_http.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport socket\nimport threading\nimport time\nfrom collections.abc import Callable\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nimport pytest\n\nfrom tests.response import Response\nfrom uvicorn import Server\nfrom uvicorn._types import ASGIApplication, ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.config import WS_PROTOCOLS, Config\nfrom uvicorn.lifespan.off import LifespanOff\nfrom uvicorn.lifespan.on import LifespanOn\nfrom uvicorn.protocols.http.h11_impl import H11Protocol\nfrom uvicorn.server import ServerState\n\ntry:\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n\n    skip_if_no_httptools = pytest.mark.skipif(False, reason=\"httptools is installed\")\nexcept ModuleNotFoundError:  # pragma: no cover\n    skip_if_no_httptools = pytest.mark.skipif(True, reason=\"httptools is not installed\")\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol\n\n    WSProtocol: TypeAlias = WebSocketProtocol | _WSProtocol\n    HTTPProtocol: TypeAlias = H11Protocol | HttpToolsProtocol\n\npytestmark = pytest.mark.anyio\n\n\nWEBSOCKET_PROTOCOLS = WS_PROTOCOLS.keys()\n\nSIMPLE_GET_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.1\", b\"Host: example.org\", b\"\", b\"\"])\n\nSIMPLE_HEAD_REQUEST = b\"\\r\\n\".join([b\"HEAD / HTTP/1.1\", b\"Host: example.org\", b\"\", b\"\"])\n\nSIMPLE_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: application/json\",\n        b\"Content-Length: 18\",\n        b\"\",\n        b'{\"hello\": \"world\"}',\n    ]\n)\n\nCONNECTION_CLOSE_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.1\", b\"Host: example.org\", b\"Connection: close\", b\"\", b\"\"])\n\nCONNECTION_CLOSE_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Connection: close\",\n        b\"Content-Type: application/json\",\n        b\"Content-Length: 18\",\n        b\"\",\n        b\"{'hello': 'world'}\",\n    ]\n)\n\nREQUEST_AFTER_CONNECTION_CLOSE = b\"\\r\\n\".join(\n    [\n        b\"GET / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Connection: close\",\n        b\"\",\n        b\"\",\n        b\"GET / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nLARGE_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: text/plain\",\n        b\"Content-Length: 100000\",\n        b\"\",\n        b\"x\" * 100000,\n    ]\n)\n\nSTART_POST_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"POST / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Content-Type: application/json\",\n        b\"Content-Length: 18\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nFINISH_POST_REQUEST = b'{\"hello\": \"world\"}'\n\nHTTP10_GET_REQUEST = b\"\\r\\n\".join([b\"GET / HTTP/1.0\", b\"Host: example.org\", b\"\", b\"\"])\n\nGET_REQUEST_WITH_RAW_PATH = b\"\\r\\n\".join([b\"GET /one%2Ftwo HTTP/1.1\", b\"Host: example.org\", b\"\", b\"\"])\n\nUPGRADE_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"GET / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Connection: upgrade\",\n        b\"Upgrade: websocket\",\n        b\"Sec-WebSocket-Version: 11\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nUPGRADE_HTTP2_REQUEST = b\"\\r\\n\".join(\n    [\n        b\"GET / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Connection: upgrade\",\n        b\"Upgrade: h2c\",\n        b\"Sec-WebSocket-Version: 11\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nINVALID_REQUEST_TEMPLATE = b\"\\r\\n\".join(\n    [\n        b\"%s\",\n        b\"Host: example.org\",\n        b\"\",\n        b\"\",\n    ]\n)\n\nGET_REQUEST_HUGE_HEADERS = [\n    b\"\".join(\n        [\n            b\"GET / HTTP/1.1\\r\\n\",\n            b\"Host: example.org\\r\\n\",\n            b\"Cookie: \" + b\"x\" * 32 * 1024,\n        ]\n    ),\n    b\"\".join([b\"x\" * 32 * 1024 + b\"\\r\\n\", b\"\\r\\n\", b\"\\r\\n\"]),\n]\n\nUPGRADE_REQUEST_ERROR_FIELD = b\"\\r\\n\".join(\n    [\n        b\"GET / HTTP/1.1\",\n        b\"Host: example.org\",\n        b\"Connection: upgrade\",\n        b\"Upgrade: not-websocket\",\n        b\"Sec-WebSocket-Version: 11\",\n        b\"\",\n        b\"\",\n    ]\n)\n\n\nclass MockTransport:\n    def __init__(\n        self, sockname: tuple[str, int] | None = None, peername: tuple[str, int] | None = None, sslcontext: bool = False\n    ):\n        self.sockname = (\"127.0.0.1\", 8000) if sockname is None else sockname\n        self.peername = (\"127.0.0.1\", 8001) if peername is None else peername\n        self.sslcontext = sslcontext\n        self.closed = False\n        self.buffer = b\"\"\n        self.read_paused = False\n\n    def get_extra_info(self, key: Any):\n        return {\"sockname\": self.sockname, \"peername\": self.peername, \"sslcontext\": self.sslcontext}.get(key)\n\n    def write(self, data: bytes):\n        assert not self.closed\n        self.buffer += data\n\n    def close(self):\n        assert not self.closed\n        self.closed = True\n\n    def pause_reading(self):\n        self.read_paused = True\n\n    def resume_reading(self):\n        self.read_paused = False\n\n    def is_closing(self):\n        return self.closed\n\n    def clear_buffer(self):\n        self.buffer = b\"\"\n\n    def set_protocol(self, protocol: asyncio.Protocol):\n        pass\n\n\nclass MockTimerHandle:\n    def __init__(\n        self, loop_later_list: list[MockTimerHandle], delay: float, callback: Callable[[], None], args: tuple[Any, ...]\n    ):\n        self.loop_later_list = loop_later_list\n        self.delay = delay\n        self.callback = callback\n        self.args = args\n        self.cancelled = False\n\n    def cancel(self):\n        if not self.cancelled:\n            self.cancelled = True\n            self.loop_later_list.remove(self)\n\n\nclass MockLoop:\n    def __init__(self):\n        self._tasks: list[asyncio.Task[Any]] = []\n        self._later: list[MockTimerHandle] = []\n\n    def create_task(self, coroutine: Any) -> Any:\n        self._tasks.insert(0, coroutine)\n        return MockTask()\n\n    def call_later(self, delay: float, callback: Callable[[], None], *args: Any) -> MockTimerHandle:\n        handle = MockTimerHandle(self._later, delay, callback, args)\n        self._later.insert(0, handle)\n        return handle\n\n    async def run_one(self):\n        return await self._tasks.pop()\n\n    def run_later(self, with_delay: float) -> None:\n        later: list[MockTimerHandle] = []\n        for timer_handle in self._later:\n            if with_delay >= timer_handle.delay:\n                timer_handle.callback(*timer_handle.args)\n            else:\n                later.append(timer_handle)\n        self._later = later\n\n\nclass MockTask:\n    def add_done_callback(self, callback: Callable[[], None]):\n        pass\n\n\nclass MockProtocol(asyncio.Protocol):\n    loop: MockLoop\n    transport: MockTransport\n    timeout_keep_alive_task: asyncio.TimerHandle | None\n    ws_protocol_class: type[WSProtocol] | None\n    scope: Scope\n\n\ndef get_connected_protocol(\n    app: ASGIApplication,\n    http_protocol_cls: type[HTTPProtocol],\n    lifespan: LifespanOff | LifespanOn | None = None,\n    **kwargs: Any,\n) -> MockProtocol:\n    loop = MockLoop()\n    transport = MockTransport()\n    config = Config(app=app, **kwargs)\n    lifespan = lifespan or LifespanOff(config)\n    server_state = ServerState()\n    protocol = http_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop)  # type: ignore\n    protocol.connection_made(transport)  # type: ignore[arg-type]\n    return protocol  # type: ignore[return-value]\n\n\nasync def test_get_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\n@pytest.mark.parametrize(\n    \"char\",\n    [\n        pytest.param(\"c\", id=\"allow_ascii_letter\"),\n        pytest.param(\"\\t\", id=\"allow_tab\"),\n        pytest.param(\" \", id=\"allow_space\"),\n        pytest.param(\"µ\", id=\"allow_non_ascii_char\"),\n    ],\n)\nasync def test_header_value_allowed_characters(http_protocol_cls: type[HTTPProtocol], char: str):\n    app = Response(\"Hello, world\", media_type=\"text/plain\", headers={\"key\": f\"<{char}>\"})\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert (b\"\\r\\nkey: <\" + char.encode() + b\">\\r\\n\") in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\n@pytest.mark.parametrize(\n    \"name\",\n    [\n        pytest.param(\"bad header\", id=\"reject_space\"),\n        pytest.param(\"bad\\x00header\", id=\"reject_null\"),\n        pytest.param(\"bad(header\", id=\"reject_open_paren\"),\n        pytest.param(\"bad)header\", id=\"reject_close_paren\"),\n        pytest.param(\"bad<header\", id=\"reject_less_than\"),\n        pytest.param(\"bad>header\", id=\"reject_greater_than\"),\n        pytest.param(\"bad@header\", id=\"reject_at\"),\n        pytest.param(\"bad,header\", id=\"reject_comma\"),\n        pytest.param(\"bad;header\", id=\"reject_semicolon\"),\n        pytest.param(\"bad:header\", id=\"reject_colon\"),\n        pytest.param(\"bad[header\", id=\"reject_open_bracket\"),\n        pytest.param(\"bad]header\", id=\"reject_close_bracket\"),\n        pytest.param(\"bad{header\", id=\"reject_open_brace\"),\n        pytest.param(\"bad}header\", id=\"reject_close_brace\"),\n        pytest.param(\"bad=header\", id=\"reject_equals\"),\n        pytest.param('bad\"header', id=\"reject_double_quote\"),\n        pytest.param(\"bad\\\\header\", id=\"reject_backslash\"),\n        pytest.param(\"bad\\theader\", id=\"reject_tab\"),\n        pytest.param(\"bad\\x7fheader\", id=\"reject_del\"),\n    ],\n)\nasync def test_invalid_header_name(http_protocol_cls: type[HTTPProtocol], name: str):\n    app = Response(\"Hello, world\", media_type=\"text/plain\", headers={name: \"value\"})\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    # No 500 is sent because `response_started` is set before header validation,\n    # so the error handler just closes the connection.\n    assert b\"HTTP/1.1 500 Internal Server Error\" not in protocol.transport.buffer\n    assert name.encode() not in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\n@pytest.mark.parametrize(\"path\", [\"/\", \"/?foo\", \"/?foo=bar\", \"/?foo=bar&baz=1\"])\nasync def test_request_logging(path: str, http_protocol_cls: type[HTTPProtocol], caplog: pytest.LogCaptureFixture):\n    get_request_with_query_string = b\"\\r\\n\".join(\n        [f\"GET {path} HTTP/1.1\".encode(\"ascii\"), b\"Host: example.org\", b\"\", b\"\"]\n    )\n    caplog.set_level(logging.INFO, logger=\"uvicorn.access\")\n    logging.getLogger(\"uvicorn.access\").propagate = True\n\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, log_config=None)\n    protocol.data_received(get_request_with_query_string)\n    await protocol.loop.run_one()\n    assert f'\"GET {path} HTTP/1.1\" 200' in caplog.records[0].message\n\n\nasync def test_head_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_HEAD_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" not in protocol.transport.buffer\n\n\nasync def test_post_request(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        body = b\"\"\n        more_body = True\n        while more_body:\n            message = await receive()\n            assert message[\"type\"] == \"http.request\"\n            body += message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n        response = Response(b\"Body: \" + body, media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_POST_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b'Body: {\"hello\": \"world\"}' in protocol.transport.buffer\n\n\nasync def test_keepalive(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"\", status_code=204)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n\n    assert b\"HTTP/1.1 204 No Content\" in protocol.transport.buffer\n    assert not protocol.transport.is_closing()\n\n\nasync def test_keepalive_timeout(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"\", status_code=204)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 204 No Content\" in protocol.transport.buffer\n    assert not protocol.transport.is_closing()\n    protocol.loop.run_later(with_delay=1)\n    assert not protocol.transport.is_closing()\n    protocol.loop.run_later(with_delay=5)\n    assert protocol.transport.is_closing()\n\n\nasync def test_keepalive_timeout_with_pipelined_requests(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n\n    # After processing the first request, the keep-alive task should be\n    # disabled because the second request is not responded yet.\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    assert protocol.timeout_keep_alive_task is None\n\n    # Process the second request and ensure that the keep-alive task\n    # has been enabled again as the connection is now idle.\n    protocol.transport.clear_buffer()\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    assert protocol.timeout_keep_alive_task is not None\n\n\nasync def test_close(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"\", status_code=204, headers={\"connection\": \"close\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 204 No Content\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_chunked_encoding(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"Hello, world!\", status_code=200, headers={\"transfer-encoding\": \"chunked\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"0\\r\\n\\r\\n\" in protocol.transport.buffer\n    assert not protocol.transport.is_closing()\n\n\nasync def test_chunked_encoding_empty_body(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"Hello, world!\", status_code=200, headers={\"transfer-encoding\": \"chunked\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert protocol.transport.buffer.count(b\"0\\r\\n\\r\\n\") == 1\n    assert not protocol.transport.is_closing()\n\n\nasync def test_chunked_encoding_head_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"Hello, world!\", status_code=200, headers={\"transfer-encoding\": \"chunked\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_HEAD_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert not protocol.transport.is_closing()\n\n\nasync def test_pipelined_requests(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    protocol.transport.clear_buffer()\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    protocol.transport.clear_buffer()\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    protocol.transport.clear_buffer()\n\n\nasync def test_undersized_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"xxx\", headers={\"content-length\": \"10\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert protocol.transport.is_closing()\n\n\nasync def test_oversized_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"xxx\" * 20, headers={\"content-length\": \"10\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert protocol.transport.is_closing()\n\n\nasync def test_large_post_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(LARGE_POST_REQUEST)\n    assert protocol.transport.read_paused\n    await protocol.loop.run_one()\n    assert not protocol.transport.read_paused\n\n\nasync def test_invalid_http(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(b\"x\" * 100000)\n    assert protocol.transport.is_closing()\n\n\nasync def test_app_exception(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        raise Exception()\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_exception_during_response(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n        await send({\"type\": \"http.response.body\", \"body\": b\"1\", \"more_body\": True})\n        raise Exception()\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" not in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_no_response_returned(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable): ...\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_partial_response_returned(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" not in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_response_header_splitting(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"\", headers={\"key\": \"value\\r\\nCookie: smuggled=value\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" not in protocol.transport.buffer\n    assert b\"\\r\\nCookie: smuggled=value\\r\\n\" not in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_duplicate_start_message(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" not in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_missing_start_message(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.body\", \"body\": b\"\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 500 Internal Server Error\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_message_after_body_complete(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\"})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_value_returned(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"http.response.start\", \"status\": 200})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\"})\n        return 123\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_early_disconnect(http_protocol_cls: type[HTTPProtocol]):\n    got_disconnect_event = False\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal got_disconnect_event\n\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"http.disconnect\":\n                break\n\n        got_disconnect_event = True\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_POST_REQUEST)\n    protocol.eof_received()\n    protocol.connection_lost(None)\n    await protocol.loop.run_one()\n    assert got_disconnect_event\n\n\nasync def test_early_response(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(START_POST_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    protocol.data_received(FINISH_POST_REQUEST)\n    assert not protocol.transport.is_closing()\n\n\nasync def test_read_after_response(http_protocol_cls: type[HTTPProtocol]):\n    message_after_response = None\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal message_after_response\n\n        response = Response(\"Hello, world\", media_type=\"text/plain\")\n        await response(scope, receive, send)\n        message_after_response = await receive()\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_POST_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert message_after_response == {\"type\": \"http.disconnect\"}\n\n\nasync def test_http10_request(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"http\"\n        content = \"Version: %s\" % scope[\"http_version\"]\n        response = Response(content, media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(HTTP10_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Version: 1.0\" in protocol.transport.buffer\n\n\nasync def test_root_path(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"http\"\n        root_path = scope.get(\"root_path\", \"\")\n        path = scope[\"path\"]\n        response = Response(f\"root_path={root_path} path={path}\", media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls, root_path=\"/app\")\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"root_path=/app path=/app/\" in protocol.transport.buffer\n\n\nasync def test_raw_path(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"http\"\n        path = scope[\"path\"]\n        raw_path = scope.get(\"raw_path\", None)\n        assert \"/app/one/two\" == path\n        assert b\"/app/one%2Ftwo\" == raw_path\n\n        response = Response(\"Done\", media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls, root_path=\"/app\")\n    protocol.data_received(GET_REQUEST_WITH_RAW_PATH)\n    await protocol.loop.run_one()\n    assert b\"Done\" in protocol.transport.buffer\n\n\nasync def test_max_concurrency(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, limit_concurrency=1)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert (\n        b\"\\r\\n\".join(\n            [\n                b\"HTTP/1.1 503 Service Unavailable\",\n                b\"content-type: text/plain; charset=utf-8\",\n                b\"content-length: 19\",\n                b\"connection: close\",\n                b\"\",\n                b\"Service Unavailable\",\n            ]\n        )\n        == protocol.transport.buffer\n    )\n\n\nasync def test_shutdown_during_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(b\"\", status_code=204)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    protocol.shutdown()  # type: ignore[attr-defined]\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 204 No Content\" in protocol.transport.buffer\n    assert protocol.transport.is_closing()\n\n\nasync def test_shutdown_during_idle(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.shutdown()  # type: ignore[attr-defined]\n    assert protocol.transport.buffer == b\"\"\n    assert protocol.transport.is_closing()\n\n\nasync def test_100_continue_sent_when_body_consumed(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        body = b\"\"\n        more_body = True\n        while more_body:\n            message = await receive()\n            assert message[\"type\"] == \"http.request\"\n            body += message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n        response = Response(b\"Body: \" + body, media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    EXPECT_100_REQUEST = b\"\\r\\n\".join(\n        [\n            b\"POST / HTTP/1.1\",\n            b\"Host: example.org\",\n            b\"Expect: 100-continue\",\n            b\"Content-Type: application/json\",\n            b\"Content-Length: 18\",\n            b\"\",\n            b'{\"hello\": \"world\"}',\n        ]\n    )\n    protocol.data_received(EXPECT_100_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 100 Continue\" in protocol.transport.buffer\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b'Body: {\"hello\": \"world\"}' in protocol.transport.buffer\n\n\nasync def test_100_continue_not_sent_when_body_not_consumed(\n    http_protocol_cls: type[HTTPProtocol],\n):\n    app = Response(b\"\", status_code=204)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    EXPECT_100_REQUEST = b\"\\r\\n\".join(\n        [\n            b\"POST / HTTP/1.1\",\n            b\"Host: example.org\",\n            b\"Expect: 100-continue\",\n            b\"Content-Type: application/json\",\n            b\"Content-Length: 18\",\n            b\"\",\n            b'{\"hello\": \"world\"}',\n        ]\n    )\n    protocol.data_received(EXPECT_100_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 100 Continue\" not in protocol.transport.buffer\n    assert b\"HTTP/1.1 204 No Content\" in protocol.transport.buffer\n\n\nasync def test_supported_upgrade_request(http_protocol_cls: type[HTTPProtocol]):\n    pytest.importorskip(\"wsproto\")\n\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, ws=\"wsproto\")\n    protocol.data_received(UPGRADE_REQUEST)\n    assert b\"HTTP/1.1 426 \" in protocol.transport.buffer\n\n\nasync def test_unsupported_ws_upgrade_request(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, ws=\"none\")\n    protocol.data_received(UPGRADE_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def test_unsupported_ws_upgrade_request_warn_on_auto(\n    caplog: pytest.LogCaptureFixture, http_protocol_cls: type[HTTPProtocol]\n):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, ws=\"auto\")\n    protocol.ws_protocol_class = None\n    protocol.data_received(UPGRADE_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n    warnings = [record.msg for record in filter(lambda record: record.levelname == \"WARNING\", caplog.records)]\n    assert \"Unsupported upgrade request.\" in warnings\n    msg = \"No supported WebSocket library detected. Please use \\\"pip install 'uvicorn[standard]'\\\", or install 'websockets' or 'wsproto' manually.\"  # noqa: E501\n    assert msg in warnings\n\n\nasync def test_http2_upgrade_request(http_protocol_cls: type[HTTPProtocol], ws_protocol_cls: type[WSProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, ws=ws_protocol_cls)\n    protocol.data_received(UPGRADE_HTTP2_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def asgi3app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n    pass\n\n\ndef asgi2app(scope: Scope):\n    async def asgi(receive: ASGIReceiveCallable, send: ASGISendCallable):\n        pass\n\n    return asgi\n\n\n@pytest.mark.parametrize(\n    \"asgi2or3_app, expected_scopes\",\n    [\n        (asgi3app, {\"version\": \"3.0\", \"spec_version\": \"2.3\"}),\n        (asgi2app, {\"version\": \"2.0\", \"spec_version\": \"2.3\"}),\n    ],\n)\nasync def test_scopes(\n    asgi2or3_app: ASGIApplication,\n    expected_scopes: dict[str, str],\n    http_protocol_cls: type[HTTPProtocol],\n):\n    protocol = get_connected_protocol(asgi2or3_app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert expected_scopes == protocol.scope.get(\"asgi\")\n\n\n@pytest.mark.parametrize(\n    \"request_line\",\n    [\n        pytest.param(b\"G?T / HTTP/1.1\", id=\"invalid-method\"),\n        pytest.param(b\"GET /?x=y z HTTP/1.1\", id=\"invalid-path\"),\n        pytest.param(b\"GET / HTTP1.1\", id=\"invalid-http-version\"),\n    ],\n)\nasync def test_invalid_http_request(\n    request_line: str, http_protocol_cls: type[HTTPProtocol], caplog: pytest.LogCaptureFixture\n):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n    request = INVALID_REQUEST_TEMPLATE % request_line\n\n    caplog.set_level(logging.INFO, logger=\"uvicorn.error\")\n    logging.getLogger(\"uvicorn.error\").propagate = True\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(request)\n    assert b\"HTTP/1.1 400 Bad Request\" in protocol.transport.buffer\n    assert b\"Invalid HTTP request received.\" in protocol.transport.buffer\n\n\n@skip_if_no_httptools\ndef test_fragmentation(unused_tcp_port: int):\n    def receive_all(sock: socket.socket):\n        chunks: list[bytes] = []\n        while True:\n            chunk = sock.recv(1024)\n            if not chunk:\n                break\n            chunks.append(chunk)\n        return b\"\".join(chunks)\n\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    def send_fragmented_req(path: str):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.connect((\"127.0.0.1\", unused_tcp_port))\n        d = (f\"GET {path} HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\").encode()\n        split = len(path) // 2\n        sock.sendall(d[:split])\n        time.sleep(0.01)\n        sock.sendall(d[split:])\n        resp = receive_all(sock)\n        # see https://github.com/kmonsoor/py-amqplib/issues/45\n        # we skip the error on bsd systems if python is too slow\n        try:\n            sock.shutdown(socket.SHUT_RDWR)\n        except Exception:  # pragma: no cover\n            pass\n        sock.close()\n        return resp\n\n    config = Config(app=app, http=\"httptools\", port=unused_tcp_port)\n    server = Server(config=config)\n    t = threading.Thread(target=server.run)\n    t.daemon = True\n    t.start()\n    time.sleep(1)  # wait for uvicorn to start\n\n    path = \"/?param=\" + \"q\" * 10\n    response = send_fragmented_req(path)\n    bad_response = b\"HTTP/1.1 400 Bad Request\"\n    assert bad_response != response[: len(bad_response)]\n    server.should_exit = True\n    t.join()\n\n\nasync def test_huge_headers_h11protocol_failure():\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, H11Protocol)\n    # Huge headers make h11 fail in it's default config\n    # h11 sends back a 400 in this case\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[0])\n    assert b\"HTTP/1.1 400 Bad Request\" in protocol.transport.buffer\n    assert b\"Connection: close\" in protocol.transport.buffer\n    assert b\"Invalid HTTP request received.\" in protocol.transport.buffer\n\n\n@skip_if_no_httptools\nasync def test_huge_headers_httptools_will_pass():\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, HttpToolsProtocol)\n    # Huge headers make h11 fail in it's default config\n    # httptools protocol will always pass\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[0])\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[1])\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def test_huge_headers_h11protocol_failure_with_setting():\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, H11Protocol, h11_max_incomplete_event_size=20 * 1024)\n    # Huge headers make h11 fail in it's default config\n    # h11 sends back a 400 in this case\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[0])\n    assert b\"HTTP/1.1 400 Bad Request\" in protocol.transport.buffer\n    assert b\"Connection: close\" in protocol.transport.buffer\n    assert b\"Invalid HTTP request received.\" in protocol.transport.buffer\n\n\n@skip_if_no_httptools\nasync def test_huge_headers_httptools():\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, HttpToolsProtocol)\n    # Huge headers make h11 fail in it's default config\n    # httptools protocol will always pass\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[0])\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[1])\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def test_huge_headers_h11_max_incomplete():\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, H11Protocol, h11_max_incomplete_event_size=64 * 1024)\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[0])\n    protocol.data_received(GET_REQUEST_HUGE_HEADERS[1])\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def test_return_close_header(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(CONNECTION_CLOSE_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"content-type: text/plain\" in protocol.transport.buffer\n    assert b\"content-length: 12\" in protocol.transport.buffer\n    # NOTE: We need to use `.lower()` because H11 implementation doesn't allow Uvicorn\n    # to lowercase them. See: https://github.com/python-hyper/h11/issues/156\n    assert b\"connection: close\" in protocol.transport.buffer.lower()\n\n\nasync def test_close_connection_with_multiple_requests(http_protocol_cls: type[HTTPProtocol]):\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(REQUEST_AFTER_CONNECTION_CLOSE)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"content-type: text/plain\" in protocol.transport.buffer\n    assert b\"content-length: 12\" in protocol.transport.buffer\n    # NOTE: We need to use `.lower()` because H11 implementation doesn't allow Uvicorn\n    # to lowercase them. See: https://github.com/python-hyper/h11/issues/156\n    assert b\"connection: close\" in protocol.transport.buffer.lower()\n\n\nasync def test_close_connection_with_post_request(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        body = b\"\"\n        more_body = True\n        while more_body:\n            message = await receive()\n            assert message[\"type\"] == \"http.request\"\n            body += message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n        response = Response(b\"Body: \" + body, media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(CONNECTION_CLOSE_POST_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Body: {'hello': 'world'}\" in protocol.transport.buffer\n\n\nasync def test_iterator_headers(http_protocol_cls: type[HTTPProtocol]):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        headers = iter([(b\"x-test-header\", b\"test value\")])\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": headers})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\"})\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(SIMPLE_GET_REQUEST)\n    await protocol.loop.run_one()\n    assert b\"x-test-header: test value\" in protocol.transport.buffer\n\n\nasync def test_lifespan_state(http_protocol_cls: type[HTTPProtocol]):\n    expected_states = [{\"a\": 123, \"b\": [1]}, {\"a\": 123, \"b\": [1, 2]}]\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert \"state\" in scope\n        expected_state = expected_states.pop(0)\n        assert scope[\"state\"] == expected_state\n        # modifications to keys are not preserved\n        scope[\"state\"][\"a\"] = 456\n        # unless of course the value itself is mutated\n        scope[\"state\"][\"b\"].append(2)\n        return await Response(\"Hi!\")(scope, receive, send)\n\n    lifespan = LifespanOn(config=Config(app=app))\n    # skip over actually running the lifespan, that is tested\n    # in the lifespan tests\n    lifespan.state.update({\"a\": 123, \"b\": [1]})\n\n    protocol = get_connected_protocol(app, http_protocol_cls, lifespan=lifespan)\n    for _ in range(2):\n        protocol.data_received(SIMPLE_GET_REQUEST)\n        await protocol.loop.run_one()\n        assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n        assert b\"Hi!\" in protocol.transport.buffer\n\n    assert not expected_states  # consumed\n\n\nasync def test_header_upgrade_is_not_websocket_depend_installed(\n    caplog: pytest.LogCaptureFixture, http_protocol_cls: type[HTTPProtocol]\n):\n    caplog.set_level(logging.WARNING, logger=\"uvicorn.error\")\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls)\n    protocol.data_received(UPGRADE_REQUEST_ERROR_FIELD)\n    await protocol.loop.run_one()\n    assert \"Unsupported upgrade request.\" in caplog.text\n    msg = \"No supported WebSocket library detected. Please use \\\"pip install 'uvicorn[standard]'\\\", or install 'websockets' or 'wsproto' manually.\"  # noqa: E501\n    assert msg not in caplog.text\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n\n\nasync def test_header_upgrade_is_websocket_depend_not_installed(\n    caplog: pytest.LogCaptureFixture, http_protocol_cls: type[HTTPProtocol]\n):\n    caplog.set_level(logging.WARNING, logger=\"uvicorn.error\")\n    app = Response(\"Hello, world\", media_type=\"text/plain\")\n\n    protocol = get_connected_protocol(app, http_protocol_cls, ws=\"none\")\n    protocol.data_received(UPGRADE_REQUEST_ERROR_FIELD)\n    await protocol.loop.run_one()\n    assert \"Unsupported upgrade request.\" in caplog.text\n    msg = \"No supported WebSocket library detected. Please use \\\"pip install 'uvicorn[standard]'\\\", or install 'websockets' or 'wsproto' manually.\"  # noqa: E501\n    assert msg in caplog.text\n    assert b\"HTTP/1.1 200 OK\" in protocol.transport.buffer\n    assert b\"Hello, world\" in protocol.transport.buffer\n"
  },
  {
    "path": "tests/protocols/test_utils.py",
    "content": "from __future__ import annotations\n\nimport socket\nfrom asyncio import Transport\nfrom typing import Any\n\nimport pytest\n\nfrom uvicorn.protocols.utils import get_client_addr, get_local_addr, get_remote_addr\n\n\nclass MockSocket:\n    def __init__(\n        self,\n        family: socket.AddressFamily,\n        peername: tuple[str, int] | None = None,\n        sockname: tuple[str, int] | str | None = None,\n    ):\n        self.peername = peername\n        self.sockname = sockname\n        self.family = family\n\n    def getpeername(self):\n        return self.peername\n\n    def getsockname(self):\n        return self.sockname\n\n\nclass MockTransport(Transport):\n    def __init__(self, info: dict[str, Any]) -> None:\n        self.info = info\n\n    def get_extra_info(self, name: str, default: Any = None) -> Any:\n        return self.info.get(name)\n\n\ndef test_get_local_addr_with_socket():\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_IPX)})\n    assert get_local_addr(transport) is None\n\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_INET6, sockname=(\"::1\", 123))})\n    assert get_local_addr(transport) == (\"::1\", 123)\n\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_INET, sockname=(\"123.45.6.7\", 123))})\n    assert get_local_addr(transport) == (\"123.45.6.7\", 123)\n\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_INET, sockname=\"/tmp/test.sock\")})\n    assert get_local_addr(transport) == (\"/tmp/test.sock\", None)\n\n\ndef test_get_remote_addr_with_socket():\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_IPX)})\n    assert get_remote_addr(transport) is None\n\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_INET6, peername=(\"::1\", 123))})\n    assert get_remote_addr(transport) == (\"::1\", 123)\n\n    transport = MockTransport({\"socket\": MockSocket(family=socket.AF_INET, peername=(\"123.45.6.7\", 123))})\n    assert get_remote_addr(transport) == (\"123.45.6.7\", 123)\n\n    if hasattr(socket, \"AF_UNIX\"):  # pragma: no cover\n        transport = MockTransport({\"socket\": MockSocket(family=socket.AF_UNIX, peername=(\"127.0.0.1\", 8000))})\n        assert get_remote_addr(transport) == (\"127.0.0.1\", 8000)\n\n\ndef test_get_local_addr():\n    transport = MockTransport({\"sockname\": \"path/to/unix-domain-socket\"})\n    assert get_local_addr(transport) == (\"path/to/unix-domain-socket\", None)\n\n    transport = MockTransport({\"sockname\": (\"123.45.6.7\", 123)})\n    assert get_local_addr(transport) == (\"123.45.6.7\", 123)\n\n    transport = MockTransport({})\n    assert get_local_addr(transport) is None\n\n\ndef test_get_remote_addr():\n    transport = MockTransport({\"peername\": None})\n    assert get_remote_addr(transport) is None\n\n    transport = MockTransport({\"peername\": (\"123.45.6.7\", 123)})\n    assert get_remote_addr(transport) == (\"123.45.6.7\", 123)\n\n\n@pytest.mark.parametrize(\n    \"scope, expected_client\",\n    [({\"client\": (\"127.0.0.1\", 36000)}, \"127.0.0.1:36000\"), ({\"client\": None}, \"\")],\n    ids=[\"ip:port client\", \"None client\"],\n)\ndef test_get_client_addr(scope: Any, expected_client: str):\n    assert get_client_addr(scope) == expected_client\n"
  },
  {
    "path": "tests/protocols/test_websocket.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom copy import deepcopy\nfrom typing import TYPE_CHECKING, Any, TypeAlias, TypedDict\n\nimport httpx\nimport pytest\nimport websockets\nimport websockets.client\nimport websockets.exceptions\nfrom websockets.extensions.permessage_deflate import ClientPerMessageDeflateFactory\nfrom websockets.typing import Subprotocol\n\nfrom tests.response import Response\nfrom tests.utils import run_server\nfrom uvicorn._types import (\n    ASGIReceiveCallable,\n    ASGIReceiveEvent,\n    ASGISendCallable,\n    Scope,\n    WebSocketCloseEvent,\n    WebSocketConnectEvent,\n    WebSocketDisconnectEvent,\n    WebSocketReceiveEvent,\n    WebSocketResponseStartEvent,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n\ntry:\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol\n\n    skip_if_no_wsproto = pytest.mark.skipif(False, reason=\"wsproto is installed.\")\nexcept ModuleNotFoundError:  # pragma: no cover\n    skip_if_no_wsproto = pytest.mark.skipif(True, reason=\"wsproto is not installed.\")\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.http.h11_impl import H11Protocol\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol\n\n    HTTPProtocol: TypeAlias = \"type[H11Protocol | HttpToolsProtocol]\"\n    WSProtocol: TypeAlias = \"type[_WSProtocol | WebSocketProtocol]\"\n\npytestmark = pytest.mark.anyio\n\n\nclass WebSocketResponse:\n    def __init__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self):\n        return self.asgi().__await__()\n\n    async def asgi(self):\n        while True:\n            message = await self.receive()\n            message_type = message[\"type\"].replace(\".\", \"_\")\n            handler = getattr(self, message_type, None)\n            if handler is not None:\n                await handler(message)\n            if message_type == \"websocket_disconnect\":\n                break\n\n\nasync def wsresponse(url: str):\n    \"\"\"\n    A simple websocket connection request and response helper\n    \"\"\"\n    url = url.replace(\"ws:\", \"http:\")\n    headers = {\n        \"connection\": \"upgrade\",\n        \"upgrade\": \"websocket\",\n        \"Sec-WebSocket-Key\": \"x3JJHMbDL1EzLkh9GBhXDw==\",\n        \"Sec-WebSocket-Version\": \"13\",\n    }\n    async with httpx.AsyncClient() as client:\n        return await client.get(url, headers=headers)\n\n\nasync def test_invalid_upgrade(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    def app(scope: Scope):\n        return None\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(\n                f\"http://127.0.0.1:{unused_tcp_port}\",\n                headers={\n                    \"upgrade\": \"websocket\",\n                    \"connection\": \"upgrade\",\n                    \"sec-webSocket-version\": \"11\",\n                },\n            )\n        if response.status_code == 426:\n            # response.text == \"\"\n            pass  # ok, wsproto 0.13\n        else:\n            assert response.status_code == 400\n            assert response.text.lower().strip().rstrip(\".\") in [\n                \"missing sec-websocket-key header\",\n                \"missing sec-websocket-version header\",  # websockets\n                \"missing or empty sec-websocket-key header\",  # wsproto\n                \"failed to open a websocket connection: missing sec-websocket-key header\",\n                \"failed to open a websocket connection: missing or empty sec-websocket-key header\",\n                \"failed to open a websocket connection: missing sec-websocket-key header; 'sec-websocket-key'\",\n            ]\n\n\nasync def test_accept_connection(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.open\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert is_open\n\n\nasync def test_shutdown(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config) as server:\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\"):\n            # Attempt shutdown while connection is still open\n            await server.shutdown()\n\n\nasync def test_supports_permessage_deflate_extension(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        extension_factories = [ClientPerMessageDeflateFactory()]\n        async with websockets.client.connect(url, extensions=extension_factories) as websocket:\n            return [extension.name for extension in websocket.extensions]\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        extension_names = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert \"permessage-deflate\" in extension_names\n\n\nasync def test_can_disable_permessage_deflate_extension(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        # enable per-message deflate on the client, so that we can check the server\n        # won't support it when it's disabled.\n        extension_factories = [ClientPerMessageDeflateFactory()]\n        async with websockets.client.connect(url, extensions=extension_factories) as websocket:\n            return [extension.name for extension in websocket.extensions]\n\n    config = Config(\n        app=App,\n        ws=ws_protocol_cls,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        ws_per_message_deflate=False,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        extension_names = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert \"permessage-deflate\" not in extension_names\n\n\nasync def test_close_connection(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.close\"})\n\n    async def open_connection(url: str):\n        try:\n            await websockets.client.connect(url)\n        except websockets.exceptions.InvalidHandshake:\n            return False\n        return True  # pragma: no cover\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert not is_open\n\n\nasync def test_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            headers = self.scope.get(\"headers\")\n            headers = dict(headers)  # type: ignore\n            assert headers[b\"host\"].startswith(b\"127.0.0.1\")\n            assert headers[b\"username\"] == bytes(\"abraão\", \"utf-8\")\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url, extra_headers=[(\"username\", \"abraão\")]) as websocket:\n            return websocket.open\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert is_open\n\n\nasync def test_extra_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\", \"headers\": [(b\"extra\", b\"header\")]})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.response_headers\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        extra_headers = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert extra_headers.get(\"extra\") == \"header\"\n\n\nasync def test_path_and_raw_path(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            path = self.scope.get(\"path\")\n            raw_path = self.scope.get(\"raw_path\")\n            assert path == \"/one/two\"\n            assert raw_path == b\"/one%2Ftwo\"\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.open\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}/one%2Ftwo\")\n        assert is_open\n\n\nasync def test_send_text_data_to_client(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n            await self.send({\"type\": \"websocket.send\", \"text\": \"123\"})\n\n    async def get_data(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return await websocket.recv()\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        data = await get_data(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == \"123\"\n\n\nasync def test_send_binary_data_to_client(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n            await self.send({\"type\": \"websocket.send\", \"bytes\": b\"123\"})\n\n    async def get_data(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return await websocket.recv()\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        data = await get_data(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == b\"123\"\n\n\nasync def test_send_and_close_connection(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n            await self.send({\"type\": \"websocket.send\", \"text\": \"123\"})\n            await self.send({\"type\": \"websocket.close\"})\n\n    async def get_data(url: str):\n        async with websockets.client.connect(url) as websocket:\n            data = await websocket.recv()\n            is_open = True\n            try:\n                await websocket.recv()\n            except Exception:\n                is_open = False\n            return (data, is_open)\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        (data, is_open) = await get_data(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == \"123\"\n        assert not is_open\n\n\nasync def test_send_text_data_to_server(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n        async def websocket_receive(self, message: WebSocketReceiveEvent):\n            _text = message.get(\"text\")\n            assert _text is not None\n            await self.send({\"type\": \"websocket.send\", \"text\": _text})\n\n    async def send_text(url: str):\n        async with websockets.client.connect(url) as websocket:\n            await websocket.send(\"abc\")\n            return await websocket.recv()\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        data = await send_text(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == \"abc\"\n\n\nasync def test_send_binary_data_to_server(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n        async def websocket_receive(self, message: WebSocketReceiveEvent):\n            _bytes = message.get(\"bytes\")\n            assert _bytes is not None\n            await self.send({\"type\": \"websocket.send\", \"bytes\": _bytes})\n\n    async def send_text(url: str):\n        async with websockets.client.connect(url) as websocket:\n            await websocket.send(b\"abc\")\n            return await websocket.recv()\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        data = await send_text(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == b\"abc\"\n\n\nasync def test_send_after_protocol_close(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n            await self.send({\"type\": \"websocket.send\", \"text\": \"123\"})\n            await self.send({\"type\": \"websocket.close\"})\n            with pytest.raises(Exception):\n                await self.send({\"type\": \"websocket.send\", \"text\": \"123\"})\n\n    async def get_data(url: str):\n        async with websockets.client.connect(url) as websocket:\n            data = await websocket.recv()\n            is_open = True\n            try:\n                await websocket.recv()\n            except Exception:\n                is_open = False\n            return (data, is_open)\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        (data, is_open) = await get_data(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert data == \"123\"\n        assert not is_open\n\n\nasync def test_missing_handshake(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        pass\n\n    async def connect(url: str):\n        await websockets.client.connect(url)\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            await connect(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert exc_info.value.status_code == 500\n\n\nasync def test_send_before_handshake(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"websocket.send\", \"text\": \"123\"})\n\n    async def connect(url: str):\n        await websockets.client.connect(url)\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            await connect(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert exc_info.value.status_code == 500\n\n\nasync def test_duplicate_handshake(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"websocket.accept\"})\n        await send({\"type\": \"websocket.accept\"})\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            with pytest.raises(websockets.exceptions.ConnectionClosed):\n                _ = await websocket.recv()\n        assert websocket.close_code == 1006\n\n\nasync def test_asgi_return_value(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    \"\"\"\n    The ASGI callable should return 'None'. If it doesn't, make sure that\n    the connection is closed with an error condition.\n    \"\"\"\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        await send({\"type\": \"websocket.accept\"})\n        return 123\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            with pytest.raises(websockets.exceptions.ConnectionClosed):\n                _ = await websocket.recv()\n        assert websocket.close_code == 1006\n\n\nasync def test_close_transport_on_asgi_return(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    \"\"\"The ASGI callable should call the `websocket.close` event.\n\n    If it doesn't, the server should still send a close frame to the client.\n    \"\"\"\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        message = await receive()\n        if message[\"type\"] == \"websocket.connect\":\n            await send({\"type\": \"websocket.accept\"})\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            with pytest.raises(websockets.exceptions.ConnectionClosed):\n                await websocket.recv()\n        assert websocket.close_code == 1006\n\n\n@pytest.mark.parametrize(\"code\", [None, 1000, 1001])\n@pytest.mark.parametrize(\"reason\", [None, \"test\", False], ids=[\"none_as_reason\", \"normal_reason\", \"without_reason\"])\nasync def test_app_close(\n    ws_protocol_cls: WSProtocol,\n    http_protocol_cls: HTTPProtocol,\n    unused_tcp_port: int,\n    code: int | None,\n    reason: str | None,\n):\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"websocket.connect\":\n                await send({\"type\": \"websocket.accept\"})\n            elif message[\"type\"] == \"websocket.receive\":\n                reply: WebSocketCloseEvent = {\"type\": \"websocket.close\"}\n\n                if code is not None:\n                    reply[\"code\"] = code\n\n                if reason is not False:\n                    reply[\"reason\"] = reason\n\n                await send(reply)\n            elif message[\"type\"] == \"websocket.disconnect\":\n                break\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            await websocket.ping()\n            await websocket.send(\"abc\")\n            with pytest.raises(websockets.exceptions.ConnectionClosed):\n                await websocket.recv()\n        assert websocket.close_code == (code or 1000)\n        assert websocket.close_reason == (reason or \"\")\n\n\nasync def test_client_close(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    disconnect_message: WebSocketDisconnectEvent | None = None\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnect_message\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"websocket.connect\":\n                await send({\"type\": \"websocket.accept\"})\n            elif message[\"type\"] == \"websocket.receive\":\n                pass\n            elif message[\"type\"] == \"websocket.disconnect\":\n                disconnect_message = message\n                break\n\n    async def websocket_session(url: str):\n        async with websockets.client.connect(url) as websocket:\n            await websocket.ping()\n            await websocket.send(\"abc\")\n            await websocket.close(code=1001, reason=\"custom reason\")\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n    assert disconnect_message == {\"type\": \"websocket.disconnect\", \"code\": 1001, \"reason\": \"custom reason\"}\n\n\nasync def test_client_connection_lost(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    got_disconnect_event = False\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal got_disconnect_event\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"websocket.connect\":\n                await send({\"type\": \"websocket.accept\"})\n            elif message[\"type\"] == \"websocket.disconnect\":\n                break\n\n        got_disconnect_event = True\n\n    config = Config(\n        app=app,\n        ws=ws_protocol_cls,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        ws_ping_interval=0.0,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            websocket.transport.close()\n            await asyncio.sleep(0.1)\n            got_disconnect_event_before_shutdown = got_disconnect_event\n\n    assert got_disconnect_event_before_shutdown is True\n\n\nasync def test_client_connection_lost_on_send(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    disconnect = asyncio.Event()\n    got_disconnect_event = False\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal got_disconnect_event\n        message = await receive()\n        if message[\"type\"] == \"websocket.connect\":\n            await send({\"type\": \"websocket.accept\"})\n        try:\n            await disconnect.wait()\n            await send({\"type\": \"websocket.send\", \"text\": \"123\"})\n        except OSError:\n            got_disconnect_event = True\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        url = f\"ws://127.0.0.1:{unused_tcp_port}\"\n        async with websockets.client.connect(url):\n            await asyncio.sleep(0.1)\n        disconnect.set()\n\n    assert got_disconnect_event is True\n\n\nasync def test_connection_lost_before_handshake_complete(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    send_accept_task = asyncio.Event()\n    disconnect_message: WebSocketDisconnectEvent = {}  # type: ignore\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnect_message\n        message = await receive()\n        if message[\"type\"] == \"websocket.connect\":\n            await send_accept_task.wait()\n        disconnect_message = await receive()  # type: ignore\n\n    async def websocket_session(uri: str):\n        async with httpx.AsyncClient() as client:\n            await client.get(\n                f\"http://127.0.0.1:{unused_tcp_port}\",\n                headers={\n                    \"upgrade\": \"websocket\",\n                    \"connection\": \"upgrade\",\n                    \"sec-websocket-version\": \"13\",\n                    \"sec-websocket-key\": \"dGhlIHNhbXBsZSBub25jZQ==\",\n                },\n            )\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        task = asyncio.create_task(websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\"))\n        await asyncio.sleep(0.1)\n        send_accept_task.set()\n        await asyncio.sleep(0.1)\n\n    assert disconnect_message == {\"type\": \"websocket.disconnect\", \"code\": 1006}\n    await task\n\n\nasync def test_send_close_on_server_shutdown(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    disconnect_message: WebSocketDisconnectEvent = {}  # type: ignore\n    server_shutdown_event = asyncio.Event()\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnect_message\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"websocket.connect\":\n                await send({\"type\": \"websocket.accept\"})\n            elif message[\"type\"] == \"websocket.disconnect\":\n                disconnect_message = message\n                break\n\n    websocket: websockets.client.WebSocketClientProtocol | None = None\n\n    async def websocket_session(uri: str):\n        nonlocal websocket\n        async with websockets.client.connect(uri) as ws_connection:\n            websocket = ws_connection\n            await server_shutdown_event.wait()\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        task = asyncio.create_task(websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\"))\n        await asyncio.sleep(0.1)\n        disconnect_message_before_shutdown = disconnect_message\n    server_shutdown_event.set()\n\n    assert websocket is not None\n    assert websocket.close_code == 1012\n    assert disconnect_message_before_shutdown == {}\n    assert disconnect_message == {\"type\": \"websocket.disconnect\", \"code\": 1012}\n    task.cancel()\n\n\n@pytest.mark.parametrize(\"subprotocol\", [\"proto1\", \"proto2\"])\nasync def test_subprotocols(\n    ws_protocol_cls: WSProtocol,\n    http_protocol_cls: HTTPProtocol,\n    subprotocol: str,\n    unused_tcp_port: int,\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\", \"subprotocol\": subprotocol})\n\n    async def get_subprotocol(url: str):\n        async with websockets.client.connect(\n            url, subprotocols=[Subprotocol(\"proto1\"), Subprotocol(\"proto2\")]\n        ) as websocket:\n            return websocket.subprotocol\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        accepted_subprotocol = await get_subprotocol(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert accepted_subprotocol == subprotocol\n\n\nMAX_WS_BYTES = 1024 * 1024 * 16\nMAX_WS_BYTES_PLUS1 = MAX_WS_BYTES + 1\n\n\n@pytest.mark.parametrize(\n    \"client_size_sent, server_size_max, expected_result\",\n    [\n        (MAX_WS_BYTES, MAX_WS_BYTES, 0),\n        (MAX_WS_BYTES_PLUS1, MAX_WS_BYTES, 1009),\n        (10, 10, 0),\n        (11, 10, 1009),\n    ],\n    ids=[\n        \"max=defaults sent=defaults\",\n        \"max=defaults sent=defaults+1\",\n        \"max=10 sent=10\",\n        \"max=10 sent=11\",\n    ],\n)\nasync def test_send_binary_data_to_server_bigger_than_default_on_websockets(\n    http_protocol_cls: HTTPProtocol,\n    client_size_sent: int,\n    server_size_max: int,\n    expected_result: int,\n    unused_tcp_port: int,\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n        async def websocket_receive(self, message: WebSocketReceiveEvent):\n            _bytes = message.get(\"bytes\")\n            assert _bytes is not None\n            await self.send({\"type\": \"websocket.send\", \"bytes\": _bytes})\n\n    config = Config(\n        app=App,\n        ws=WebSocketProtocol,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        ws_max_size=server_size_max,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\", max_size=client_size_sent) as ws:\n            await ws.send(b\"\\x01\" * client_size_sent)\n            if expected_result == 0:\n                data = await ws.recv()\n                assert data == b\"\\x01\" * client_size_sent\n            else:\n                with pytest.raises(websockets.exceptions.ConnectionClosedError):\n                    await ws.recv()\n                assert ws.close_code == expected_result\n\n\nasync def test_server_reject_connection(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    disconnected_message: ASGIReceiveEvent = {}  # type: ignore\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnected_message\n        assert scope[\"type\"] == \"websocket\"\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        # Reject the connection.\n        await send({\"type\": \"websocket.close\"})\n        # -- At this point websockets' recv() is unusable. --\n\n        # This doesn't raise `TypeError`:\n        # See https://github.com/Kludex/uvicorn/issues/244\n        disconnected_message = await receive()\n\n    async def websocket_session(url: str):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            async with websockets.client.connect(url):\n                pass  # pragma: no cover\n        assert exc_info.value.status_code == 403\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n    assert disconnected_message == {\"type\": \"websocket.disconnect\", \"code\": 1006}\n\n\nclass EmptyDict(TypedDict): ...\n\n\nasync def test_server_reject_connection_with_response(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    disconnected_message: WebSocketDisconnectEvent | EmptyDict = {}\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnected_message\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope and \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        # Reject the connection with a response\n        response = Response(b\"goodbye\", status_code=400)\n        await response(scope, receive, send)\n        disconnected_message = await receive()\n\n    async def websocket_session(url: str):\n        response = await wsresponse(url)\n        assert response.status_code == 400\n        assert response.content == b\"goodbye\"\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n    assert disconnected_message == {\"type\": \"websocket.disconnect\", \"code\": 1006}\n\n\nasync def test_server_reject_connection_with_multibody_response(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    disconnected_message: ASGIReceiveEvent = {}  # type: ignore\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal disconnected_message\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope\n        assert \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n        await send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": 400,\n                \"headers\": [\n                    (b\"Content-Length\", b\"20\"),\n                    (b\"Content-Type\", b\"text/plain\"),\n                ],\n            }\n        )\n        await send({\"type\": \"websocket.http.response.body\", \"body\": b\"x\" * 10, \"more_body\": True})\n        await send({\"type\": \"websocket.http.response.body\", \"body\": b\"y\" * 10})\n        disconnected_message = await receive()\n\n    async def websocket_session(url: str):\n        response = await wsresponse(url)\n        assert response.status_code == 400\n        assert response.content == (b\"x\" * 10) + (b\"y\" * 10)\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n    assert disconnected_message == {\"type\": \"websocket.disconnect\", \"code\": 1006}\n\n\nasync def test_server_reject_connection_with_invalid_status(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    # this test checks that even if there is an error in the response, the server\n    # can successfully send a 500 error back to the client\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope and \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        await send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": 700,  # invalid status code\n                \"headers\": [(b\"Content-Length\", b\"0\"), (b\"Content-Type\", b\"text/plain\")],\n            }\n        )\n\n    async def websocket_session(url: str):\n        response = await wsresponse(url)\n        assert response.status_code == 500\n        assert response.content == b\"Internal Server Error\"\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n\nasync def test_server_reject_connection_with_body_nolength(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    # test that the server can send a response with a body but no content-length\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope\n        assert \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        await send({\"type\": \"websocket.http.response.start\", \"status\": 403, \"headers\": []})\n        await send({\"type\": \"websocket.http.response.body\", \"body\": b\"hardbody\"})\n\n    async def websocket_session(url: str):\n        response = await wsresponse(url)\n        assert response.status_code == 403\n        assert response.content == b\"hardbody\"\n        if ws_protocol_cls == _WSProtocol:\n            # wsproto automatically makes the message chunked\n            assert response.headers[\"transfer-encoding\"] == \"chunked\"\n        else:\n            # websockets automatically adds a content-length\n            assert response.headers[\"content-length\"] == \"8\"\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n\nasync def test_server_reject_connection_with_invalid_msg(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    if ws_protocol_cls.__name__ == \"WebSocketsSansIOProtocol\":\n        pytest.skip(\"WebSocketsSansIOProtocol sends both start and body messages in one message.\")\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope and \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message_rcvd = await receive()\n        assert message_rcvd[\"type\"] == \"websocket.connect\"\n\n        message: WebSocketResponseStartEvent = {\n            \"type\": \"websocket.http.response.start\",\n            \"status\": 404,\n            \"headers\": [(b\"Content-Length\", b\"0\"), (b\"Content-Type\", b\"text/plain\")],\n        }\n        await send(message)\n        # send invalid message.  This will raise an exception here\n        await send(message)\n\n    async def websocket_session(url: str):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            async with websockets.client.connect(url):\n                pass  # pragma: no cover\n        assert exc_info.value.status_code == 404\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n\nasync def test_server_reject_connection_with_missing_body(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    if ws_protocol_cls.__name__ == \"WebSocketsSansIOProtocol\":\n        pytest.skip(\"WebSocketsSansIOProtocol sends both start and body messages in one message.\")\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope and \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        await send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": 404,\n                \"headers\": [(b\"Content-Length\", b\"0\"), (b\"Content-Type\", b\"text/plain\")],\n            }\n        )\n        # no further message\n\n    async def websocket_session(url: str):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            async with websockets.client.connect(url):\n                pass  # pragma: no cover\n        assert exc_info.value.status_code == 404\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n\nasync def test_server_multiple_websocket_http_response_start_events(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    \"\"\"\n    The server should raise an exception if it sends multiple\n    websocket.http.response.start events.\n    \"\"\"\n    if ws_protocol_cls.__name__ == \"WebSocketsSansIOProtocol\":\n        pytest.skip(\"WebSocketsSansIOProtocol sends both start and body messages in one message.\")\n    exception_message: str | None = None\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        nonlocal exception_message\n        assert scope[\"type\"] == \"websocket\"\n        assert \"extensions\" in scope\n        assert \"websocket.http.response\" in scope[\"extensions\"]\n\n        # Pull up first recv message.\n        message = await receive()\n        assert message[\"type\"] == \"websocket.connect\"\n\n        start_event: WebSocketResponseStartEvent = {\n            \"type\": \"websocket.http.response.start\",\n            \"status\": 404,\n            \"headers\": [(b\"Content-Length\", b\"0\"), (b\"Content-Type\", b\"text/plain\")],\n        }\n        await send(start_event)\n        try:\n            await send(start_event)\n        except Exception as exc:\n            exception_message = str(exc)\n\n    async def websocket_session(url: str):\n        with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:\n            async with websockets.client.connect(url):\n                pass  # pragma: no cover\n        assert exc_info.value.status_code == 404\n\n    config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        await websocket_session(f\"ws://127.0.0.1:{unused_tcp_port}\")\n\n    assert exception_message == (\n        \"Expected ASGI message 'websocket.http.response.body' but got 'websocket.http.response.start'.\"\n    )\n\n\nasync def test_server_can_read_messages_in_buffer_after_close(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    frames: list[bytes] = []\n    disconnect_message: WebSocketDisconnectEvent | EmptyDict = {}\n\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n            # Ensure server doesn't start reading frames from read buffer until\n            # after client has sent close frame, but server is still able to\n            # read these frames\n            await asyncio.sleep(0.2)\n\n        async def websocket_disconnect(self, message: WebSocketDisconnectEvent):\n            nonlocal disconnect_message\n            disconnect_message = message\n\n        async def websocket_receive(self, message: WebSocketReceiveEvent):\n            _bytes = message.get(\"bytes\")\n            assert _bytes is not None\n            frames.append(_bytes)\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        async with websockets.client.connect(f\"ws://127.0.0.1:{unused_tcp_port}\") as websocket:\n            await websocket.send(b\"abc\")\n            await websocket.send(b\"abc\")\n            await websocket.send(b\"abc\")\n\n    assert frames == [b\"abc\", b\"abc\", b\"abc\"]\n    assert disconnect_message == {\"type\": \"websocket.disconnect\", \"code\": 1000, \"reason\": \"\"}\n\n\nasync def test_default_server_headers(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.response_headers\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        headers = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert headers.get(\"server\") == \"uvicorn\" and \"date\" in headers\n\n\nasync def test_no_server_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.response_headers\n\n    config = Config(\n        app=App,\n        ws=ws_protocol_cls,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        server_header=False,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        headers = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert \"server\" not in headers\n\n\n@skip_if_no_wsproto\nasync def test_no_date_header_on_wsproto(http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.response_headers\n\n    config = Config(\n        app=App,\n        ws=_WSProtocol,\n        http=http_protocol_cls,\n        lifespan=\"off\",\n        date_header=False,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        headers = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert \"date\" not in headers\n\n\nasync def test_multiple_server_header(\n    ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int\n):\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            await self.send(\n                {\n                    \"type\": \"websocket.accept\",\n                    \"headers\": [\n                        (b\"Server\", b\"over-ridden\"),\n                        (b\"Server\", b\"another-value\"),\n                    ],\n                }\n            )\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.response_headers\n\n    config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"off\", port=unused_tcp_port)\n    async with run_server(config):\n        headers = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert headers.get_all(\"Server\") == [\"uvicorn\", \"over-ridden\", \"another-value\"]\n\n\nasync def test_lifespan_state(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):\n    expected_states: list[dict[str, Any]] = [\n        {\"a\": 123, \"b\": [1]},\n        {\"a\": 123, \"b\": [1, 2]},\n    ]\n\n    actual_states: list[dict[str, Any]] = []\n\n    async def lifespan_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\" and \"state\" in scope\n        scope[\"state\"][\"a\"] = 123\n        scope[\"state\"][\"b\"] = [1]\n        await send({\"type\": \"lifespan.startup.complete\"})\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.shutdown\"\n        await send({\"type\": \"lifespan.shutdown.complete\"})\n\n    class App(WebSocketResponse):\n        async def websocket_connect(self, message: WebSocketConnectEvent):\n            assert \"state\" in self.scope\n            actual_states.append(deepcopy(self.scope[\"state\"]))\n            self.scope[\"state\"][\"a\"] = 456\n            self.scope[\"state\"][\"b\"].append(2)\n            await self.send({\"type\": \"websocket.accept\"})\n\n    async def open_connection(url: str):\n        async with websockets.client.connect(url) as websocket:\n            return websocket.open\n\n    async def app_wrapper(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        if scope[\"type\"] == \"lifespan\":\n            return await lifespan_app(scope, receive, send)\n        return await App(scope, receive, send)\n\n    config = Config(app=app_wrapper, ws=ws_protocol_cls, http=http_protocol_cls, lifespan=\"on\", port=unused_tcp_port)\n    async with run_server(config):\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert is_open\n        is_open = await open_connection(f\"ws://127.0.0.1:{unused_tcp_port}\")\n        assert is_open\n\n    assert expected_states == actual_states\n"
  },
  {
    "path": "tests/response.py",
    "content": "class Response:\n    charset = \"utf-8\"\n\n    def __init__(self, content, status_code=200, headers=None, media_type=None):\n        self.body = self.render(content)\n        self.status_code = status_code\n        self.headers = headers or {}\n        self.media_type = media_type\n        self.set_content_type()\n        self.set_content_length()\n\n    async def __call__(self, scope, receive, send) -> None:\n        prefix = \"websocket.\" if scope[\"type\"] == \"websocket\" else \"\"\n        await send(\n            {\n                \"type\": prefix + \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": [[key.encode(), value.encode()] for key, value in self.headers.items()],\n            }\n        )\n        await send({\"type\": prefix + \"http.response.body\", \"body\": self.body})\n\n    def render(self, content) -> bytes:\n        if isinstance(content, bytes):\n            return content\n        return content.encode(self.charset)\n\n    def set_content_length(self):\n        if \"content-length\" not in self.headers:\n            self.headers[\"content-length\"] = str(len(self.body))\n\n    def set_content_type(self):\n        if self.media_type is not None and \"content-type\" not in self.headers:\n            content_type = self.media_type\n            if content_type.startswith(\"text/\") and self.charset is not None:\n                content_type += \"; charset=%s\" % self.charset\n            self.headers[\"content-type\"] = content_type\n"
  },
  {
    "path": "tests/supervisors/__init__.py",
    "content": ""
  },
  {
    "path": "tests/supervisors/test_multiprocess.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport os\nimport signal\nimport socket\nimport threading\nimport time\nfrom collections.abc import Callable\nfrom typing import Any\n\nimport pytest\n\nfrom uvicorn import Config\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.supervisors import Multiprocess\nfrom uvicorn.supervisors.multiprocess import Process\n\n\ndef new_console_in_windows(test_function: Callable[[], Any]) -> Callable[[], Any]:  # pragma: no cover\n    if os.name != \"nt\":\n        return test_function\n\n    @functools.wraps(test_function)\n    def new_function():\n        import subprocess\n        import sys\n\n        module = test_function.__module__\n        name = test_function.__name__\n\n        subprocess.check_call(\n            [sys.executable, \"-c\", f\"from {module} import {name}; {name}.__wrapped__()\"],\n            creationflags=subprocess.CREATE_NO_WINDOW,\n        )\n\n    return new_function\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    pass  # pragma: no cover\n\n\ndef run(sockets: list[socket.socket] | None) -> None:\n    while True:  # pragma: no cover\n        time.sleep(1)\n\n\ndef test_process_ping_pong() -> None:\n    process = Process(Config(app=app), target=lambda x: None, sockets=[])\n    threading.Thread(target=process.always_pong, daemon=True).start()\n    assert process.ping()\n\n\ndef test_process_ping_pong_timeout() -> None:\n    process = Process(Config(app=app), target=lambda x: None, sockets=[])\n    assert not process.ping(0.1)\n\n\n@new_console_in_windows\ndef test_multiprocess_run() -> None:\n    \"\"\"\n    A basic sanity check.\n\n    Simply run the supervisor against a no-op server, and signal for it to\n    quit immediately.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    supervisor.signal_queue.append(signal.SIGINT)\n    supervisor.join_all()\n\n\n@new_console_in_windows\ndef test_multiprocess_health_check() -> None:\n    \"\"\"\n    Ensure that the health check works as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    time.sleep(1)\n    process = supervisor.processes[0]\n    process.kill()\n    assert not process.is_alive()\n    deadline = time.monotonic() + 10\n    while not all(p.is_alive() for p in supervisor.processes):  # pragma: no cover\n        assert time.monotonic() < deadline, \"Timed out waiting for processes to be alive\"\n        time.sleep(0.1)\n    supervisor.signal_queue.append(signal.SIGINT)\n    supervisor.join_all()\n\n\n@new_console_in_windows\ndef test_multiprocess_sigterm() -> None:\n    \"\"\"\n    Ensure that the SIGTERM signal is handled as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    time.sleep(1)\n    supervisor.signal_queue.append(signal.SIGTERM)\n    supervisor.join_all()\n\n\n@pytest.mark.skipif(not hasattr(signal, \"SIGBREAK\"), reason=\"platform unsupports SIGBREAK\")\n@new_console_in_windows\ndef test_multiprocess_sigbreak() -> None:  # pragma: py-not-win32\n    \"\"\"\n    Ensure that the SIGBREAK signal is handled as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    time.sleep(1)\n    supervisor.signal_queue.append(getattr(signal, \"SIGBREAK\"))\n    supervisor.join_all()\n\n\n@pytest.mark.skipif(not hasattr(signal, \"SIGHUP\"), reason=\"platform unsupports SIGHUP\")\ndef test_multiprocess_sighup() -> None:\n    \"\"\"\n    Ensure that the SIGHUP signal is handled as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    time.sleep(1)\n    pids = [p.pid for p in supervisor.processes]\n    supervisor.signal_queue.append(signal.SIGHUP)\n    # Poll instead of a fixed sleep — the supervisor loop runs on a 0.5s interval and `restart_all()` terminates/joins\n    # each worker sequentially, so the total time is non-deterministic.\n    deadline = time.monotonic() + 10\n    while time.monotonic() < deadline:\n        if [p.pid for p in supervisor.processes] != pids:\n            break\n        time.sleep(0.1)\n    assert pids != [p.pid for p in supervisor.processes]\n    supervisor.signal_queue.append(signal.SIGINT)\n    supervisor.join_all()\n\n\n@pytest.mark.skipif(not hasattr(signal, \"SIGTTIN\"), reason=\"platform unsupports SIGTTIN\")\ndef test_multiprocess_sigttin() -> None:\n    \"\"\"\n    Ensure that the SIGTTIN signal is handled as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    supervisor.signal_queue.append(signal.SIGTTIN)\n    time.sleep(1)\n    assert len(supervisor.processes) == 3\n    supervisor.signal_queue.append(signal.SIGINT)\n    supervisor.join_all()\n\n\n@pytest.mark.skipif(not hasattr(signal, \"SIGTTOU\"), reason=\"platform unsupports SIGTTOU\")\ndef test_multiprocess_sigttou() -> None:\n    \"\"\"\n    Ensure that the SIGTTOU signal is handled as expected.\n    \"\"\"\n    config = Config(app=app, workers=2)\n    supervisor = Multiprocess(config, target=run, sockets=[])\n    threading.Thread(target=supervisor.run, daemon=True).start()\n    supervisor.signal_queue.append(signal.SIGTTOU)\n    time.sleep(1)\n    assert len(supervisor.processes) == 1\n    supervisor.signal_queue.append(signal.SIGTTOU)\n    time.sleep(1)\n    assert len(supervisor.processes) == 1\n    supervisor.signal_queue.append(signal.SIGINT)\n    supervisor.join_all()\n"
  },
  {
    "path": "tests/supervisors/test_reload.py",
    "content": "from __future__ import annotations\n\nimport signal\nimport socket\nimport sys\nfrom collections.abc import Callable, Generator\nfrom pathlib import Path\nfrom threading import Thread\nfrom time import sleep\n\nimport pytest\nfrom pytest_mock import MockerFixture\n\nfrom tests.utils import as_cwd\nfrom uvicorn.config import Config\nfrom uvicorn.supervisors.basereload import BaseReload, _display_path\nfrom uvicorn.supervisors.statreload import StatReload\n\ntry:\n    from uvicorn.supervisors.watchfilesreload import WatchFilesReload\nexcept ImportError:  # pragma: no cover\n    WatchFilesReload = None  # type: ignore[misc,assignment]\n\n\n# TODO: Investigate why this is flaky on MacOS, and Windows.\nskip_non_linux = pytest.mark.skipif(sys.platform in (\"darwin\", \"win32\"), reason=\"Flaky on Windows and MacOS\")\n\n\ndef run(sockets: list[socket.socket] | None) -> None:\n    pass  # pragma: no cover\n\n\ndef sleep_touch(*paths: Path):\n    sleep(0.1)\n    for p in paths:\n        p.touch()\n\n\n@pytest.fixture\ndef touch_soon() -> Generator[Callable[[Path], None]]:\n    threads: list[Thread] = []\n\n    def start(*paths: Path) -> None:\n        thread = Thread(target=sleep_touch, args=paths)\n        thread.start()\n        threads.append(thread)\n\n    yield start\n\n    for t in threads:\n        t.join()\n\n\nclass TestBaseReload:\n    @pytest.fixture(autouse=True)\n    def setup(self, reload_directory_structure: Path, reloader_class: type[BaseReload] | None):\n        if reloader_class is None:  # pragma: no cover\n            pytest.skip(\"Needed dependency not installed\")\n        self.reload_path = reload_directory_structure\n        self.reloader_class = reloader_class\n\n    def _setup_reloader(self, config: Config) -> BaseReload:\n        config.reload_delay = 0  # save time\n\n        reloader = self.reloader_class(config, target=run, sockets=[])\n\n        assert config.should_reload\n        reloader.startup()\n        return reloader\n\n    def _reload_tester(\n        self, touch_soon: Callable[[Path], None], reloader: BaseReload, *files: Path\n    ) -> list[Path] | None:\n        reloader.restart()\n        if WatchFilesReload is not None and isinstance(reloader, WatchFilesReload):\n            touch_soon(*files)\n        else:\n            assert not next(reloader)\n            sleep(0.1)\n            for file in files:\n                file.touch()\n        return next(reloader)\n\n    @pytest.mark.parametrize(\"reloader_class\", [StatReload, WatchFilesReload])\n    def test_reloader_should_initialize(self) -> None:\n        \"\"\"\n        A basic sanity check.\n\n        Simply run the reloader against a no-op server, and signal for it to\n        quit immediately.\n        \"\"\"\n        with as_cwd(self.reload_path):\n            config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n            reloader = self._setup_reloader(config)\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [StatReload, pytest.param(WatchFilesReload, marks=skip_non_linux)])\n    def test_reload_when_python_file_is_changed(self, touch_soon: Callable[[Path], None]):\n        file = self.reload_path / \"main.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n            reloader = self._setup_reloader(config)\n\n            changes = self._reload_tester(touch_soon, reloader, file)\n            assert changes == [file]\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [StatReload, WatchFilesReload])\n    def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon: Callable[[Path], None]):\n        file = self.reload_path / \"app\" / \"sub\" / \"sub.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n            reloader = self._setup_reloader(config)\n\n            assert self._reload_tester(touch_soon, reloader, file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [WatchFilesReload])\n    def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self, touch_soon: Callable[[Path], None]):\n        sub_dir = self.reload_path / \"app\" / \"sub\"\n        sub_file = sub_dir / \"sub.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_excludes=[str(sub_dir)],\n            )\n            reloader = self._setup_reloader(config)\n\n            assert not self._reload_tester(touch_soon, reloader, sub_file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\n        \"reloader_class, result\", [(StatReload, False), pytest.param(WatchFilesReload, True, marks=skip_non_linux)]\n    )\n    def test_reload_when_pattern_matched_file_is_changed(\n        self, result: bool, touch_soon: Callable[[Path], None]\n    ):  # pragma: py-not-linux\n        file = self.reload_path / \"app\" / \"js\" / \"main.js\"\n\n        with as_cwd(self.reload_path):\n            config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_includes=[\"*.js\"])\n            reloader = self._setup_reloader(config)\n\n            assert bool(self._reload_tester(touch_soon, reloader, file)) == result\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [pytest.param(WatchFilesReload, marks=skip_non_linux)])\n    def test_should_not_reload_when_exclude_pattern_match_file_is_changed(\n        self, touch_soon: Callable[[Path], None]\n    ):  # pragma: py-not-linux\n        python_file = self.reload_path / \"app\" / \"src\" / \"main.py\"\n        css_file = self.reload_path / \"app\" / \"css\" / \"main.css\"\n        js_file = self.reload_path / \"app\" / \"js\" / \"main.js\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_includes=[\"*\"],\n                reload_excludes=[\"*.js\"],\n            )\n            reloader = self._setup_reloader(config)\n\n            assert self._reload_tester(touch_soon, reloader, python_file)\n            assert self._reload_tester(touch_soon, reloader, css_file)\n            assert not self._reload_tester(touch_soon, reloader, js_file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [StatReload, WatchFilesReload])\n    def test_should_not_reload_when_dot_file_is_changed(self, touch_soon: Callable[[Path], None]):\n        file = self.reload_path / \".dotted\"\n\n        with as_cwd(self.reload_path):\n            config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n            reloader = self._setup_reloader(config)\n\n            assert not self._reload_tester(touch_soon, reloader, file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [StatReload, pytest.param(WatchFilesReload, marks=skip_non_linux)])\n    def test_should_reload_when_directories_have_same_prefix(\n        self, touch_soon: Callable[[Path], None]\n    ):  # pragma: py-not-linux\n        app_dir = self.reload_path / \"app\"\n        app_file = app_dir / \"src\" / \"main.py\"\n        app_first_dir = self.reload_path / \"app_first\"\n        app_first_file = app_first_dir / \"src\" / \"main.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_dirs=[str(app_dir), str(app_first_dir)],\n            )\n            reloader = self._setup_reloader(config)\n\n            assert self._reload_tester(touch_soon, reloader, app_file)\n            assert self._reload_tester(touch_soon, reloader, app_first_file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\n        \"reloader_class\",\n        [StatReload, pytest.param(WatchFilesReload, marks=skip_non_linux)],\n    )\n    def test_should_not_reload_when_only_subdirectory_is_watched(\n        self, touch_soon: Callable[[Path], None]\n    ):  # pragma: py-not-linux\n        app_dir = self.reload_path / \"app\"\n        app_dir_file = self.reload_path / \"app\" / \"src\" / \"main.py\"\n        root_file = self.reload_path / \"main.py\"\n\n        config = Config(\n            app=\"tests.test_config:asgi_app\",\n            reload=True,\n            reload_dirs=[str(app_dir)],\n        )\n        reloader = self._setup_reloader(config)\n\n        assert self._reload_tester(touch_soon, reloader, app_dir_file)\n        assert not self._reload_tester(touch_soon, reloader, root_file, app_dir / \"~ignored\")\n\n        reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [pytest.param(WatchFilesReload, marks=skip_non_linux)])\n    def test_override_defaults(self, touch_soon: Callable[[Path], None]) -> None:  # pragma: py-not-linux\n        dotted_file = self.reload_path / \".dotted\"\n        dotted_dir_file = self.reload_path / \".dotted_dir\" / \"file.txt\"\n        python_file = self.reload_path / \"main.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                # We need to add *.txt otherwise no regular files will match\n                reload_includes=[\".*\", \"*.txt\"],\n                reload_excludes=[\"*.py\"],\n            )\n            reloader = self._setup_reloader(config)\n\n            assert self._reload_tester(touch_soon, reloader, dotted_file)\n            assert self._reload_tester(touch_soon, reloader, dotted_dir_file)\n            assert not self._reload_tester(touch_soon, reloader, python_file)\n\n            reloader.shutdown()\n\n    @pytest.mark.parametrize(\"reloader_class\", [pytest.param(WatchFilesReload, marks=skip_non_linux)])\n    def test_explicit_paths(self, touch_soon: Callable[[Path], None]) -> None:  # pragma: py-not-linux\n        dotted_file = self.reload_path / \".dotted\"\n        non_dotted_file = self.reload_path / \"ext\" / \"ext.jpg\"\n        python_file = self.reload_path / \"main.py\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_includes=[\".dotted\", \"ext/ext.jpg\"],\n            )\n            reloader = self._setup_reloader(config)\n\n            assert self._reload_tester(touch_soon, reloader, dotted_file)\n            assert self._reload_tester(touch_soon, reloader, non_dotted_file)\n            assert self._reload_tester(touch_soon, reloader, python_file)\n\n            reloader.shutdown()\n\n    @pytest.mark.skipif(WatchFilesReload is None, reason=\"watchfiles not available\")\n    @pytest.mark.parametrize(\"reloader_class\", [WatchFilesReload])\n    def test_watchfiles_no_changes(self) -> None:\n        sub_dir = self.reload_path / \"app\" / \"sub\"\n\n        with as_cwd(self.reload_path):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_excludes=[str(sub_dir)],\n            )\n            reloader = self._setup_reloader(config)\n\n            from watchfiles import watch\n\n            assert isinstance(reloader, WatchFilesReload)\n            # just so we can make rust_timeout 100ms\n            reloader.watcher = watch(\n                sub_dir,\n                watch_filter=None,\n                stop_event=reloader.should_exit,\n                yield_on_timeout=True,\n                rust_timeout=100,\n            )\n\n            assert reloader.should_restart() is None\n\n            reloader.shutdown()\n\n\n@pytest.mark.skipif(WatchFilesReload is None, reason=\"watchfiles not available\")\ndef test_should_watch_cwd(mocker: MockerFixture, reload_directory_structure: Path):\n    mock_watch = mocker.patch(\"uvicorn.supervisors.watchfilesreload.watch\")\n\n    config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_dirs=[])\n    WatchFilesReload(config, target=run, sockets=[])\n    mock_watch.assert_called_once()\n    assert mock_watch.call_args[0] == (Path.cwd(),)\n\n\n@pytest.mark.skipif(WatchFilesReload is None, reason=\"watchfiles not available\")\ndef test_should_watch_multiple_dirs(mocker: MockerFixture, reload_directory_structure: Path):\n    mock_watch = mocker.patch(\"uvicorn.supervisors.watchfilesreload.watch\")\n    app_dir = reload_directory_structure / \"app\"\n    app_first_dir = reload_directory_structure / \"app_first\"\n    config = Config(\n        app=\"tests.test_config:asgi_app\",\n        reload=True,\n        reload_dirs=[str(app_dir), str(app_first_dir)],\n    )\n    WatchFilesReload(config, target=run, sockets=[])\n    mock_watch.assert_called_once()\n    assert set(mock_watch.call_args[0]) == {\n        app_dir,\n        app_first_dir,\n    }\n\n\ndef test_display_path_relative(tmp_path: Path):\n    with as_cwd(tmp_path):\n        p = tmp_path / \"app\" / \"foobar.py\"\n        # accept windows paths as wells as posix\n        assert _display_path(p) in (\"'app/foobar.py'\", \"'app\\\\foobar.py'\")\n\n\ndef test_display_path_non_relative():\n    p = Path(\"/foo/bar.py\")\n    assert _display_path(p) in (\"'/foo/bar.py'\", \"'\\\\foo\\\\bar.py'\")\n\n\ndef test_base_reloader_run(tmp_path: Path):\n    calls: list[str] = []\n    step = 0\n\n    class CustomReload(BaseReload):\n        def startup(self):\n            calls.append(\"startup\")\n\n        def restart(self):\n            calls.append(\"restart\")\n\n        def shutdown(self):\n            calls.append(\"shutdown\")\n\n        def should_restart(self):\n            nonlocal step\n            step += 1\n            if step == 1:\n                return None\n            elif step == 2:\n                return [tmp_path / \"foobar.py\"]\n            else:\n                raise StopIteration()\n\n    config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n    reloader = CustomReload(config, target=run, sockets=[])\n    reloader.run()\n\n    assert calls == [\"startup\", \"restart\", \"shutdown\"]\n\n\ndef test_base_reloader_should_exit(tmp_path: Path):\n    config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n    reloader = BaseReload(config, target=run, sockets=[])\n    assert not reloader.should_exit.is_set()\n    reloader.pause()\n\n    if sys.platform == \"win32\":\n        reloader.signal_handler(signal.CTRL_C_EVENT, None)  # pragma: py-not-win32\n    else:\n        reloader.signal_handler(signal.SIGINT, None)  # pragma: py-win32\n\n    assert reloader.should_exit.is_set()\n    with pytest.raises(StopIteration):\n        reloader.pause()\n\n\ndef test_base_reloader_closes_sockets_on_shutdown():\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    config = Config(app=\"tests.test_config:asgi_app\", reload=True)\n    reloader = BaseReload(config, target=run, sockets=[sock])\n    reloader.startup()\n    assert sock.fileno() != -1\n    reloader.shutdown()\n    assert sock.fileno() == -1\n"
  },
  {
    "path": "tests/supervisors/test_signal.py",
    "content": "import asyncio\nimport signal\nfrom asyncio import Event\n\nimport httpx\nimport pytest\n\nfrom tests.utils import assert_signal, run_server\nfrom uvicorn import Server\nfrom uvicorn.config import Config\n\n\n@pytest.mark.anyio\nasync def test_sigint_finish_req(unused_tcp_port: int):\n    \"\"\"\n    1. Request is sent\n    2. Sigint is sent to uvicorn\n    3. Shutdown sequence start\n    4. Request is finished before timeout_graceful_shutdown=1\n\n    Result: Request should go through, even though the server was cancelled.\n    \"\"\"\n\n    server_event = Event()\n\n    async def wait_app(scope, receive, send):\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"start\", \"more_body\": True})\n        await server_event.wait()\n        await send({\"type\": \"http.response.body\", \"body\": b\"end\", \"more_body\": False})\n\n    config = Config(app=wait_app, reload=False, port=unused_tcp_port, timeout_graceful_shutdown=1)\n    server: Server\n    with assert_signal(signal.SIGINT):\n        async with run_server(config) as server, httpx.AsyncClient() as client:\n            req = asyncio.create_task(client.get(f\"http://127.0.0.1:{unused_tcp_port}\"))\n            await asyncio.sleep(0.1)  # ensure next tick\n            server.handle_exit(sig=signal.SIGINT, frame=None)  # exit\n            server_event.set()  # continue request\n            # ensure httpx has processed the response and result is complete\n            await req\n            assert req.result().status_code == 200\n            await asyncio.sleep(0.1)  # ensure shutdown is complete\n\n\n@pytest.mark.anyio\nasync def test_sigint_abort_req(unused_tcp_port: int, caplog):\n    \"\"\"\n    1. Request is sent\n    2. Sigint is sent to uvicorn\n    3. Shutdown sequence start\n    4. Request is _NOT_ finished before timeout_graceful_shutdown=1\n\n    Result: Request is cancelled mid-execution, and httpx will raise a\n        `RemoteProtocolError`.\n    \"\"\"\n\n    async def forever_app(scope, receive, send):\n        server_event = Event()\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"start\", \"more_body\": True})\n        # we never continue this one, so this request will time out\n        await server_event.wait()\n        await send({\"type\": \"http.response.body\", \"body\": b\"end\", \"more_body\": False})  # pragma: full coverage\n\n    config = Config(app=forever_app, reload=False, port=unused_tcp_port, timeout_graceful_shutdown=1)\n    server: Server\n    with assert_signal(signal.SIGINT):\n        async with run_server(config) as server, httpx.AsyncClient() as client:\n            req = asyncio.create_task(client.get(f\"http://127.0.0.1:{unused_tcp_port}\"))\n            await asyncio.sleep(0.1)  # next tick\n            # trigger exit, this request should time out in ~1 sec\n            server.handle_exit(sig=signal.SIGINT, frame=None)\n            with pytest.raises(httpx.RemoteProtocolError):\n                await req\n\n        # req.result()\n    assert \"Cancel 1 running task(s), timeout graceful shutdown exceeded\" in caplog.messages\n\n\n@pytest.mark.anyio\nasync def test_sigint_deny_request_after_triggered(unused_tcp_port: int, caplog):\n    \"\"\"\n    1. Server is started\n    2. Shutdown sequence start\n    3. Request is sent, but not accepted\n\n    Result: Request should fail, and not be able to be sent, since server is no longer\n        accepting connections.\n    \"\"\"\n\n    async def app(scope, receive, send):\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await asyncio.sleep(1)  # pragma: full coverage\n\n    config = Config(app=app, reload=False, port=unused_tcp_port, timeout_graceful_shutdown=1)\n    server: Server\n    with assert_signal(signal.SIGINT):\n        async with run_server(config) as server, httpx.AsyncClient() as client:\n            # exit and ensure we do not accept more requests\n            server.handle_exit(sig=signal.SIGINT, frame=None)\n            await asyncio.sleep(0.1)  # next tick\n            with pytest.raises(httpx.ConnectError):\n                await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n"
  },
  {
    "path": "tests/test_auto_detection.py",
    "content": "import asyncio\nimport contextlib\nimport importlib\n\nimport pytest\n\nfrom uvicorn.config import Config\nfrom uvicorn.loops.auto import auto_loop_factory\nfrom uvicorn.protocols.http.auto import AutoHTTPProtocol\nfrom uvicorn.protocols.websockets.auto import AutoWebSocketsProtocol\nfrom uvicorn.server import ServerState\n\ntry:\n    importlib.import_module(\"uvloop\")\n    expected_loop = \"uvloop\"  # pragma: py-win32\nexcept ImportError:  # pragma: py-not-win32\n    expected_loop = \"asyncio\"\n\ntry:\n    importlib.import_module(\"httptools\")\n    expected_http = \"HttpToolsProtocol\"\nexcept ImportError:  # pragma: no cover\n    expected_http = \"H11Protocol\"\n\ntry:\n    importlib.import_module(\"websockets\")\n    expected_websockets = \"WebSocketProtocol\"\nexcept ImportError:  # pragma: no cover\n    expected_websockets = \"WSProtocol\"\n\n\nasync def app(scope, receive, send):\n    pass  # pragma: no cover\n\n\ndef test_loop_auto():\n    loop_factory = auto_loop_factory(use_subprocess=True)\n    with contextlib.closing(loop_factory()) as loop:\n        assert isinstance(loop, asyncio.AbstractEventLoop)\n        assert type(loop).__module__.startswith(expected_loop)\n\n\n@pytest.mark.anyio\nasync def test_http_auto():\n    config = Config(app=app)\n    server_state = ServerState()\n    protocol = AutoHTTPProtocol(config=config, server_state=server_state, app_state={})\n    assert type(protocol).__name__ == expected_http\n\n\n@pytest.mark.anyio\nasync def test_websocket_auto():\n    config = Config(app=app)\n    server_state = ServerState()\n\n    assert AutoWebSocketsProtocol is not None\n    protocol = AutoWebSocketsProtocol(config=config, server_state=server_state, app_state={})\n    assert type(protocol).__name__ == expected_websockets\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "import contextlib\nimport importlib\nimport os\nimport platform\nimport sys\nfrom collections.abc import Iterator\nfrom pathlib import Path\nfrom textwrap import dedent\nfrom unittest import mock\n\nimport pytest\nfrom click.testing import CliRunner\n\nimport uvicorn\nfrom uvicorn.config import Config\nfrom uvicorn.main import main as cli\nfrom uvicorn.server import Server\nfrom uvicorn.supervisors import ChangeReload, Multiprocess\n\nHEADERS = \"Content-Security-Policy:default-src 'self'; script-src https://example.com\"\nmain = importlib.import_module(\"uvicorn.main\")\n\n\n@contextlib.contextmanager\ndef load_env_var(key: str, value: str) -> Iterator[None]:\n    old_environ = dict(os.environ)\n    os.environ[key] = value\n    yield\n    os.environ.clear()\n    os.environ.update(old_environ)\n\n\nclass App:\n    pass\n\n\ndef test_cli_print_version() -> None:\n    runner = CliRunner()\n\n    result = runner.invoke(cli, [\"--version\"])\n\n    assert result.exit_code == 0\n    assert (\n        \"Running uvicorn {version} with {py_implementation} {py_version} on {system}\".format(  # noqa: UP032\n            version=uvicorn.__version__,\n            py_implementation=platform.python_implementation(),\n            py_version=platform.python_version(),\n            system=platform.system(),\n        )\n    ) in result.output\n\n\ndef test_cli_headers() -> None:\n    runner = CliRunner()\n\n    with mock.patch.object(main, \"run\") as mock_run:\n        result = runner.invoke(cli, [\"tests.test_cli:App\", \"--header\", HEADERS])\n\n    assert result.output == \"\"\n    assert result.exit_code == 0\n    mock_run.assert_called_once()\n    assert mock_run.call_args[1][\"headers\"] == [\n        [\n            \"Content-Security-Policy\",\n            \"default-src 'self'; script-src https://example.com\",\n        ]\n    ]\n\n\ndef test_cli_call_server_run() -> None:\n    runner = CliRunner()\n\n    with mock.patch.object(Server, \"run\") as mock_run:\n        result = runner.invoke(cli, [\"tests.test_cli:App\"])\n\n    assert result.exit_code == 3\n    mock_run.assert_called_once()\n\n\ndef test_cli_call_change_reload_run() -> None:\n    runner = CliRunner()\n\n    with mock.patch.object(Config, \"bind_socket\") as mock_bind_socket:\n        with mock.patch.object(ChangeReload, \"run\") as mock_run:\n            result = runner.invoke(cli, [\"tests.test_cli:App\", \"--reload\"])\n\n    assert result.exit_code == 0\n    mock_bind_socket.assert_called_once()\n    mock_run.assert_called_once()\n\n\ndef test_cli_call_multiprocess_run() -> None:\n    runner = CliRunner()\n\n    with mock.patch.object(Config, \"bind_socket\") as mock_bind_socket:\n        with mock.patch.object(Multiprocess, \"run\") as mock_run:\n            result = runner.invoke(cli, [\"tests.test_cli:App\", \"--workers=2\"])\n\n    assert result.exit_code == 0\n    mock_bind_socket.assert_called_once()\n    mock_run.assert_called_once()\n\n\n@pytest.fixture(params=(True, False))\ndef uds_file(tmp_path: Path, request: pytest.FixtureRequest) -> Path:  # pragma: py-win32\n    file = tmp_path / \"uvicorn.sock\"\n    should_create_file = request.param\n    if should_create_file:\n        file.touch(exist_ok=True)\n    return file\n\n\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"require unix-like system\")\ndef test_cli_uds(uds_file: Path) -> None:  # pragma: py-win32\n    runner = CliRunner()\n\n    with mock.patch.object(Config, \"bind_socket\") as mock_bind_socket:\n        with mock.patch.object(Multiprocess, \"run\") as mock_run:\n            result = runner.invoke(cli, [\"tests.test_cli:App\", \"--workers=2\", \"--uds\", str(uds_file)])\n\n    assert result.exit_code == 0\n    assert result.output == \"\"\n    mock_bind_socket.assert_called_once()\n    mock_run.assert_called_once()\n    assert not uds_file.exists()\n\n\ndef test_cli_incomplete_app_parameter() -> None:\n    runner = CliRunner()\n\n    result = runner.invoke(cli, [\"tests.test_cli\"])\n\n    assert (\n        'Error loading ASGI app. Import string \"tests.test_cli\" must be in format \"<module>:<attribute>\".'\n    ) in result.output\n    assert result.exit_code == 1\n\n\ndef test_cli_event_size() -> None:\n    runner = CliRunner()\n\n    with mock.patch.object(main, \"run\") as mock_run:\n        result = runner.invoke(\n            cli,\n            [\"tests.test_cli:App\", \"--h11-max-incomplete-event-size\", str(32 * 1024)],\n        )\n\n    assert result.output == \"\"\n    assert result.exit_code == 0\n    mock_run.assert_called_once()\n    assert mock_run.call_args[1][\"h11_max_incomplete_event_size\"] == 32768\n\n\n@pytest.mark.parametrize(\"http_protocol\", [\"h11\", \"httptools\"])\ndef test_env_variables(http_protocol: str):\n    with load_env_var(\"UVICORN_HTTP\", http_protocol):\n        runner = CliRunner(env=os.environ)\n        with mock.patch.object(main, \"run\") as mock_run:\n            runner.invoke(cli, [\"tests.test_cli:App\"])\n            _, kwargs = mock_run.call_args\n            assert kwargs[\"http\"] == http_protocol\n\n\ndef test_ignore_environment_variable_when_set_on_cli():\n    with load_env_var(\"UVICORN_HTTP\", \"h11\"):\n        runner = CliRunner(env=os.environ)\n        with mock.patch.object(main, \"run\") as mock_run:\n            runner.invoke(cli, [\"tests.test_cli:App\", \"--http=httptools\"])\n            _, kwargs = mock_run.call_args\n            assert kwargs[\"http\"] == \"httptools\"\n\n\ndef test_app_dir(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:\n    app_dir = tmp_path / \"dir\" / \"app_dir\"\n    app_file = app_dir / \"main.py\"\n    app_dir.mkdir(parents=True)\n    app_file.touch()\n    app_file.write_text(\n        dedent(\n            \"\"\"\n            async def app(scope, receive, send):\n                ...\n            \"\"\"\n        )\n    )\n    runner = CliRunner()\n    with mock.patch.object(Server, \"run\") as mock_run:\n        result = runner.invoke(cli, [\"main:app\", \"--app-dir\", f\"{str(app_dir)}\"])\n\n    assert result.exit_code == 3\n    mock_run.assert_called_once()\n    assert sys.path[0] == str(app_dir)\n\n\ndef test_set_app_via_environment_variable():\n    app_path = \"tests.test_cli:App\"\n    with load_env_var(\"UVICORN_APP\", app_path):\n        runner = CliRunner(env=os.environ)\n        with mock.patch.object(main, \"run\") as mock_run:\n            result = runner.invoke(cli)\n            args, _ = mock_run.call_args\n            assert result.exit_code == 0\n            assert args == (app_path,)\n"
  },
  {
    "path": "tests/test_compat.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom asyncio import AbstractEventLoop\n\nimport pytest\n\nfrom tests.custom_loop_utils import CustomLoop\nfrom tests.utils import get_asyncio_default_loop_per_os\nfrom uvicorn._compat import asyncio_run\n\n\nasync def assert_event_loop(expected_loop_class: type[AbstractEventLoop]):\n    assert isinstance(asyncio.get_running_loop(), expected_loop_class)\n\n\ndef test_asyncio_run__default_loop_factory() -> None:\n    asyncio_run(assert_event_loop(get_asyncio_default_loop_per_os()), loop_factory=None)\n\n\ndef test_asyncio_run__custom_loop_factory() -> None:\n    asyncio_run(assert_event_loop(CustomLoop), loop_factory=CustomLoop)\n\n\ndef test_asyncio_run__passing_a_non_awaitable_callback_should_throw_error() -> None:\n    # TypeError on Python >= 3.14\n    with pytest.raises((ValueError, TypeError)):\n        asyncio_run(lambda: None, loop_factory=CustomLoop)  # type: ignore\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "from __future__ import annotations\n\nimport configparser\nimport io\nimport json\nimport logging\nimport os\nimport socket\nimport sys\nfrom collections.abc import Callable, Iterator\nfrom contextlib import closing\nfrom pathlib import Path\nfrom typing import IO, Any, Literal\nfrom unittest.mock import MagicMock\n\nimport pytest\nimport yaml\nfrom pytest_mock import MockerFixture\n\nfrom tests.custom_loop_utils import CustomLoop\nfrom tests.utils import as_cwd, get_asyncio_default_loop_per_os\nfrom uvicorn._types import ASGIApplication, ASGIReceiveCallable, ASGISendCallable, Environ, Scope, StartResponse\nfrom uvicorn.config import Config, LoopFactoryType\nfrom uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware\nfrom uvicorn.middleware.wsgi import WSGIMiddleware\nfrom uvicorn.protocols.http.h11_impl import H11Protocol\n\n\n@pytest.fixture\ndef mocked_logging_config_module(mocker: MockerFixture) -> MagicMock:\n    return mocker.patch(\"logging.config\")\n\n\n@pytest.fixture\ndef json_logging_config(logging_config: dict) -> str:\n    return json.dumps(logging_config)\n\n\n@pytest.fixture\ndef yaml_logging_config(logging_config: dict) -> str:\n    return yaml.dump(logging_config)\n\n\nasync def asgi_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    pass  # pragma: nocover\n\n\ndef wsgi_app(environ: Environ, start_response: StartResponse) -> None:\n    pass  # pragma: nocover\n\n\n@pytest.mark.parametrize(\n    \"app, expected_should_reload\",\n    [(asgi_app, False), (\"tests.test_config:asgi_app\", True)],\n)\ndef test_config_should_reload_is_set(app: ASGIApplication, expected_should_reload: bool) -> None:\n    config = Config(app=app, reload=True)\n    assert config.reload is True\n    assert config.should_reload is expected_should_reload\n\n\ndef test_should_warn_on_invalid_reload_configuration(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:\n    config_class = Config(app=asgi_app, reload_dirs=[str(tmp_path)])\n    assert not config_class.should_reload\n    assert len(caplog.records) == 1\n    assert (\n        caplog.records[-1].message == \"Current configuration will not reload as not all conditions are met, \"\n        \"please refer to documentation.\"\n    )\n\n    config_no_reload = Config(app=\"tests.test_config:asgi_app\", reload_dirs=[str(tmp_path)])\n    assert not config_no_reload.should_reload\n    assert len(caplog.records) == 2\n    assert (\n        caplog.records[-1].message == \"Current configuration will not reload as not all conditions are met, \"\n        \"please refer to documentation.\"\n    )\n\n\ndef test_reload_dir_is_set(reload_directory_structure: Path, caplog: pytest.LogCaptureFixture) -> None:\n    app_dir = reload_directory_structure / \"app\"\n    with caplog.at_level(logging.INFO):\n        config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_dirs=[str(app_dir)])\n        assert len(caplog.records) == 1\n        assert caplog.records[-1].message == f\"Will watch for changes in these directories: {[str(app_dir)]}\"\n        assert config.reload_dirs == [app_dir]\n        config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_dirs=str(app_dir))\n        assert config.reload_dirs == [app_dir]\n\n\ndef test_non_existant_reload_dir_is_not_set(reload_directory_structure: Path, caplog: pytest.LogCaptureFixture) -> None:\n    with as_cwd(reload_directory_structure), caplog.at_level(logging.WARNING):\n        config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_dirs=[\"reload\"])\n        assert config.reload_dirs == [reload_directory_structure]\n        assert (\n            caplog.records[-1].message\n            == \"Provided reload directories ['reload'] did not contain valid \"\n            + \"directories, watching current working directory.\"\n        )\n\n\ndef test_reload_subdir_removal(reload_directory_structure: Path) -> None:\n    app_dir = reload_directory_structure / \"app\"\n\n    reload_dirs = [str(reload_directory_structure), \"app\", str(app_dir)]\n\n    with as_cwd(reload_directory_structure):\n        config = Config(app=\"tests.test_config:asgi_app\", reload=True, reload_dirs=reload_dirs)\n        assert config.reload_dirs == [reload_directory_structure]\n\n\ndef test_reload_included_dir_is_added_to_reload_dirs(\n    reload_directory_structure: Path,\n) -> None:\n    app_dir = reload_directory_structure / \"app\"\n    ext_dir = reload_directory_structure / \"ext\"\n\n    with as_cwd(reload_directory_structure):\n        config = Config(\n            app=\"tests.test_config:asgi_app\",\n            reload=True,\n            reload_dirs=[str(app_dir)],\n            reload_includes=[\"*.js\", str(ext_dir)],\n        )\n        assert frozenset(config.reload_dirs), frozenset([app_dir, ext_dir])\n        assert frozenset(config.reload_includes) == frozenset([\"*.js\", str(ext_dir)])\n\n\ndef test_reload_dir_subdirectories_are_removed(\n    reload_directory_structure: Path,\n) -> None:\n    app_dir = reload_directory_structure / \"app\"\n    app_sub_dir = app_dir / \"sub\"\n    ext_dir = reload_directory_structure / \"ext\"\n    ext_sub_dir = ext_dir / \"sub\"\n\n    with as_cwd(reload_directory_structure):\n        config = Config(\n            app=\"tests.test_config:asgi_app\",\n            reload=True,\n            reload_dirs=[\n                str(app_dir),\n                str(app_sub_dir),\n                str(ext_sub_dir),\n                str(ext_dir),\n            ],\n        )\n        assert frozenset(config.reload_dirs) == frozenset([app_dir, ext_dir])\n\n\ndef test_reload_excluded_subdirectories_are_removed(\n    reload_directory_structure: Path,\n) -> None:\n    app_dir = reload_directory_structure / \"app\"\n    app_sub_dir = app_dir / \"sub\"\n\n    with as_cwd(reload_directory_structure):\n        config = Config(\n            app=\"tests.test_config:asgi_app\",\n            reload=True,\n            reload_excludes=[str(app_dir), str(app_sub_dir)],\n        )\n        assert frozenset(config.reload_dirs) == frozenset([reload_directory_structure])\n        assert frozenset(config.reload_dirs_excludes) == frozenset([app_dir])\n        assert frozenset(config.reload_excludes) == frozenset([str(app_dir), str(app_sub_dir)])\n\n\ndef test_reload_includes_exclude_dir_patterns_are_matched(\n    reload_directory_structure: Path, caplog: pytest.LogCaptureFixture\n) -> None:\n    with caplog.at_level(logging.INFO):\n        first_app_dir = reload_directory_structure / \"app_first\" / \"src\"\n        second_app_dir = reload_directory_structure / \"app_second\" / \"src\"\n\n        with as_cwd(reload_directory_structure):\n            config = Config(\n                app=\"tests.test_config:asgi_app\",\n                reload=True,\n                reload_includes=[\"*/src\"],\n                reload_excludes=[\"app\", \"*third*\"],\n            )\n            assert len(caplog.records) == 1\n            assert (\n                caplog.records[-1].message == \"Will watch for changes in these directories: \"\n                f\"{sorted([str(first_app_dir), str(second_app_dir)])}\"\n            )\n            assert frozenset(config.reload_dirs) == frozenset([first_app_dir, second_app_dir])\n            assert config.reload_includes == [\"*/src\"]\n\n\ndef test_wsgi_app() -> None:\n    config = Config(app=wsgi_app, interface=\"wsgi\", proxy_headers=False)\n    config.load()\n\n    assert isinstance(config.loaded_app, WSGIMiddleware)\n    assert config.interface == \"wsgi\"\n    assert config.asgi_version == \"3.0\"\n\n\ndef test_proxy_headers() -> None:\n    config = Config(app=asgi_app)\n    config.load()\n\n    assert config.proxy_headers is True\n    assert isinstance(config.loaded_app, ProxyHeadersMiddleware)\n\n\ndef test_app_unimportable_module() -> None:\n    config = Config(app=\"no.such:app\")\n    with pytest.raises(ImportError):\n        config.load()\n\n\ndef test_app_unimportable_other(caplog: pytest.LogCaptureFixture) -> None:\n    config = Config(app=\"tests.test_config:app\")\n    with pytest.raises(SystemExit):\n        config.load()\n    error_messages = [\n        record.message for record in caplog.records if record.name == \"uvicorn.error\" and record.levelname == \"ERROR\"\n    ]\n    assert (\n        'Error loading ASGI app. Attribute \"app\" not found in module \"tests.test_config\".'  # noqa: E501\n        == error_messages.pop(0)\n    )\n\n\ndef test_app_factory(caplog: pytest.LogCaptureFixture) -> None:\n    def create_app() -> ASGIApplication:\n        return asgi_app\n\n    config = Config(app=create_app, factory=True, proxy_headers=False)\n    config.load()\n    assert config.loaded_app is asgi_app\n\n    # Flag not passed. In this case, successfully load the app, but issue a warning\n    # to indicate that an explicit flag is preferred.\n    caplog.clear()\n    config = Config(app=create_app, proxy_headers=False)\n    with caplog.at_level(logging.WARNING):\n        config.load()\n    assert config.loaded_app is asgi_app\n    assert len(caplog.records) == 1\n    assert \"--factory\" in caplog.records[0].message\n\n    # App not a no-arguments callable.\n    config = Config(app=asgi_app, factory=True)\n    with pytest.raises(SystemExit):\n        config.load()\n\n\ndef test_concrete_http_class() -> None:\n    config = Config(app=asgi_app, http=H11Protocol)\n    config.load()\n    assert config.http_protocol_class is H11Protocol\n\n\ndef test_socket_bind() -> None:\n    config = Config(app=asgi_app)\n    config.load()\n    sock = config.bind_socket()\n    assert isinstance(sock, socket.socket)\n    sock.close()\n\n\ndef test_ssl_config(\n    tls_ca_certificate_pem_path: str,\n    tls_ca_certificate_private_key_path: str,\n) -> None:\n    config = Config(\n        app=asgi_app,\n        ssl_certfile=tls_ca_certificate_pem_path,\n        ssl_keyfile=tls_ca_certificate_private_key_path,\n    )\n    config.load()\n\n    assert config.is_ssl is True\n\n\ndef test_ssl_config_combined(tls_certificate_key_and_chain_path: str) -> None:\n    config = Config(\n        app=asgi_app,\n        ssl_certfile=tls_certificate_key_and_chain_path,\n    )\n    config.load()\n\n    assert config.is_ssl is True\n\n\ndef asgi2_app(scope: Scope) -> Callable:\n    async def asgi(receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:  # pragma: nocover\n        pass\n\n    return asgi  # pragma: nocover\n\n\n@pytest.mark.parametrize(\"app, expected_interface\", [(asgi_app, \"3.0\"), (asgi2_app, \"2.0\")])\ndef test_asgi_version(app: ASGIApplication, expected_interface: Literal[\"2.0\", \"3.0\"]) -> None:\n    config = Config(app=app)\n    config.load()\n    assert config.asgi_version == expected_interface\n\n\n@pytest.mark.parametrize(\n    \"use_colors, expected\",\n    [\n        pytest.param(None, None, id=\"use_colors_not_provided\"),\n        pytest.param(\"invalid\", None, id=\"use_colors_invalid_value\"),\n        pytest.param(True, True, id=\"use_colors_enabled\"),\n        pytest.param(False, False, id=\"use_colors_disabled\"),\n    ],\n)\ndef test_log_config_default(\n    mocked_logging_config_module: MagicMock,\n    use_colors: bool | None,\n    expected: bool | None,\n    logging_config: dict[str, Any],\n) -> None:\n    \"\"\"\n    Test that one can specify the use_colors option when using the default logging\n    config.\n    \"\"\"\n    config = Config(app=asgi_app, use_colors=use_colors, log_config=logging_config)\n    config.load()\n\n    mocked_logging_config_module.dictConfig.assert_called_once_with(logging_config)\n\n    (provided_dict_config,), _ = mocked_logging_config_module.dictConfig.call_args\n    assert provided_dict_config[\"formatters\"][\"default\"][\"use_colors\"] == expected\n\n\ndef test_log_config_json(\n    mocked_logging_config_module: MagicMock,\n    logging_config: dict[str, Any],\n    json_logging_config: str,\n    mocker: MockerFixture,\n) -> None:\n    \"\"\"\n    Test that one can load a json config from disk.\n    \"\"\"\n    mocked_open = mocker.patch(\"uvicorn.config.open\", mocker.mock_open(read_data=json_logging_config))\n\n    config = Config(app=asgi_app, log_config=\"log_config.json\")\n    config.load()\n\n    mocked_open.assert_called_once_with(\"log_config.json\")\n    mocked_logging_config_module.dictConfig.assert_called_once_with(logging_config)\n\n\n@pytest.mark.parametrize(\"config_filename\", [\"log_config.yml\", \"log_config.yaml\"])\ndef test_log_config_yaml(\n    mocked_logging_config_module: MagicMock,\n    logging_config: dict[str, Any],\n    yaml_logging_config: str,\n    mocker: MockerFixture,\n    config_filename: str,\n) -> None:\n    \"\"\"\n    Test that one can load a yaml config from disk.\n    \"\"\"\n    mocked_open = mocker.patch(\"uvicorn.config.open\", mocker.mock_open(read_data=yaml_logging_config))\n\n    config = Config(app=asgi_app, log_config=config_filename)\n    config.load()\n\n    mocked_open.assert_called_once_with(config_filename)\n    mocked_logging_config_module.dictConfig.assert_called_once_with(logging_config)\n\n\n@pytest.mark.parametrize(\"config_file\", [\"log_config.ini\", configparser.ConfigParser(), io.StringIO()])\ndef test_log_config_file(\n    mocked_logging_config_module: MagicMock,\n    config_file: str | configparser.RawConfigParser | IO[Any],\n) -> None:\n    \"\"\"\n    Test that one can load a configparser config from disk.\n    \"\"\"\n    config = Config(app=asgi_app, log_config=config_file)\n    config.load()\n\n    mocked_logging_config_module.fileConfig.assert_called_once_with(config_file, disable_existing_loggers=False)\n\n\n@pytest.fixture(params=[0, 1])\ndef web_concurrency(request: pytest.FixtureRequest) -> Iterator[int]:\n    yield request.param\n    if os.getenv(\"WEB_CONCURRENCY\"):\n        del os.environ[\"WEB_CONCURRENCY\"]\n\n\n@pytest.fixture(params=[\"127.0.0.1\", \"127.0.0.2\"])\ndef forwarded_allow_ips(request: pytest.FixtureRequest) -> Iterator[str]:\n    yield request.param\n    if os.getenv(\"FORWARDED_ALLOW_IPS\"):\n        del os.environ[\"FORWARDED_ALLOW_IPS\"]\n\n\ndef test_env_file(\n    web_concurrency: int,\n    forwarded_allow_ips: str,\n    caplog: pytest.LogCaptureFixture,\n    tmp_path: Path,\n) -> None:\n    \"\"\"\n    Test that one can load environment variables using an env file.\n    \"\"\"\n    fp = tmp_path / \".env\"\n    content = f\"WEB_CONCURRENCY={web_concurrency}\\nFORWARDED_ALLOW_IPS={forwarded_allow_ips}\\n\"\n    fp.write_text(content)\n    with caplog.at_level(logging.INFO):\n        config = Config(app=asgi_app, env_file=fp)\n        config.load()\n\n    assert config.workers == int(str(os.getenv(\"WEB_CONCURRENCY\")))\n    assert config.forwarded_allow_ips == os.getenv(\"FORWARDED_ALLOW_IPS\")\n    assert len(caplog.records) == 1\n    assert f\"Loading environment from '{fp}'\" in caplog.records[0].message\n\n\n@pytest.mark.parametrize(\n    \"access_log, handlers\",\n    [\n        pytest.param(True, 1, id=\"access log enabled should have single handler\"),\n        pytest.param(False, 0, id=\"access log disabled shouldn't have handlers\"),\n    ],\n)\ndef test_config_access_log(access_log: bool, handlers: int) -> None:\n    config = Config(app=asgi_app, access_log=access_log)\n    config.load()\n\n    assert len(logging.getLogger(\"uvicorn.access\").handlers) == handlers\n    assert config.access_log == access_log\n\n\n@pytest.mark.parametrize(\"log_level\", [5, 10, 20, 30, 40, 50])\ndef test_config_log_level(log_level: int) -> None:\n    config = Config(app=asgi_app, log_level=log_level)\n    config.load()\n\n    assert logging.getLogger(\"uvicorn.error\").level == log_level\n    assert logging.getLogger(\"uvicorn.access\").level == log_level\n    assert logging.getLogger(\"uvicorn.asgi\").level == log_level\n    assert config.log_level == log_level\n\n\n@pytest.mark.parametrize(\"log_level\", [None, 0, 5, 10, 20, 30, 40, 50])\n@pytest.mark.parametrize(\"uvicorn_logger_level\", [0, 5, 10, 20, 30, 40, 50])\ndef test_config_log_effective_level(log_level: int, uvicorn_logger_level: int) -> None:\n    default_level = 30\n    log_config = {\n        \"version\": 1,\n        \"disable_existing_loggers\": False,\n        \"loggers\": {\n            \"uvicorn\": {\"level\": uvicorn_logger_level},\n        },\n    }\n    config = Config(app=asgi_app, log_level=log_level, log_config=log_config)\n    config.load()\n\n    effective_level = log_level or uvicorn_logger_level or default_level\n    assert logging.getLogger(\"uvicorn.error\").getEffectiveLevel() == effective_level\n    assert logging.getLogger(\"uvicorn.access\").getEffectiveLevel() == effective_level\n    assert logging.getLogger(\"uvicorn.asgi\").getEffectiveLevel() == effective_level\n\n\ndef test_ws_max_size() -> None:\n    config = Config(app=asgi_app, ws_max_size=1000)\n    config.load()\n    assert config.ws_max_size == 1000\n\n\ndef test_ws_max_queue() -> None:\n    config = Config(app=asgi_app, ws_max_queue=64)\n    config.load()\n    assert config.ws_max_queue == 64\n\n\n@pytest.mark.parametrize(\n    \"reload, workers\",\n    [\n        (True, 1),\n        (False, 2),\n    ],\n    ids=[\"--reload=True --workers=1\", \"--reload=False --workers=2\"],\n)\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"require unix-like system\")\ndef test_bind_unix_socket_works_with_reload_or_workers(\n    tmp_path: Path, reload: bool, workers: int, short_socket_name: str\n):  # pragma: py-win32\n    config = Config(app=asgi_app, uds=short_socket_name, reload=reload, workers=workers)\n    config.load()\n    sock = config.bind_socket()\n    assert isinstance(sock, socket.socket)\n    assert sock.family == socket.AF_UNIX\n    assert sock.getsockname() == short_socket_name\n    sock.close()\n\n\n@pytest.mark.parametrize(\n    \"reload, workers\",\n    [\n        (True, 1),\n        (False, 2),\n    ],\n    ids=[\"--reload=True --workers=1\", \"--reload=False --workers=2\"],\n)\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"require unix-like system\")\ndef test_bind_fd_works_with_reload_or_workers(reload: bool, workers: int):  # pragma: py-win32\n    fdsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n    fd = fdsock.fileno()\n    config = Config(app=asgi_app, fd=fd, reload=reload, workers=workers)\n    config.load()\n    sock = config.bind_socket()\n    assert isinstance(sock, socket.socket)\n    assert sock.family == socket.AF_UNIX\n    assert sock.getsockname() == \"\"\n    sock.close()\n    fdsock.close()\n\n\n@pytest.mark.parametrize(\n    \"reload, workers, expected\",\n    [\n        (True, 1, True),\n        (False, 2, True),\n        (False, 1, False),\n    ],\n    ids=[\n        \"--reload=True --workers=1\",\n        \"--reload=False --workers=2\",\n        \"--reload=False --workers=1\",\n    ],\n)\ndef test_config_use_subprocess(reload: bool, workers: int, expected: bool):\n    config = Config(app=asgi_app, reload=reload, workers=workers)\n    config.load()\n    assert config.use_subprocess == expected\n\n\ndef test_warn_when_using_reload_and_workers(caplog: pytest.LogCaptureFixture) -> None:\n    Config(app=asgi_app, reload=True, workers=2)\n    assert len(caplog.records) == 1\n    assert '\"workers\" flag is ignored when reloading is enabled.' in caplog.records[0].message\n\n\n@pytest.mark.parametrize(\n    (\"loop_type\", \"expected_loop_factory\"),\n    [\n        (\"none\", None),\n        (\"asyncio\", get_asyncio_default_loop_per_os()),\n    ],\n)\ndef test_get_loop_factory(loop_type: LoopFactoryType, expected_loop_factory: Any):\n    config = Config(app=asgi_app, loop=loop_type)\n    loop_factory = config.get_loop_factory()\n    if loop_factory is None:\n        assert expected_loop_factory is loop_factory\n    else:\n        loop = loop_factory()\n        with closing(loop):\n            assert loop is not None\n            assert isinstance(loop, expected_loop_factory)\n\n\ndef test_custom_loop__importable_custom_loop_setup_function() -> None:\n    config = Config(app=asgi_app, loop=\"tests.custom_loop_utils:CustomLoop\")\n    config.load()\n    loop_factory = config.get_loop_factory()\n    assert loop_factory, \"Loop factory should be set\"\n    event_loop = loop_factory()\n    with closing(event_loop):\n        assert event_loop is not None\n        assert isinstance(event_loop, CustomLoop)\n\n\n@pytest.mark.filterwarnings(\"ignore::pytest.PytestUnraisableExceptionWarning\")\ndef test_custom_loop__not_importable_custom_loop_setup_function(caplog: pytest.LogCaptureFixture) -> None:\n    config = Config(app=asgi_app, loop=\"tests.test_config:non_existing_setup_function\")\n    config.load()\n    with pytest.raises(SystemExit):\n        config.get_loop_factory()\n    error_messages = [\n        record.message for record in caplog.records if record.name == \"uvicorn.error\" and record.levelname == \"ERROR\"\n    ]\n    assert (\n        'Error loading custom loop setup function. Attribute \"non_existing_setup_function\" not found in module \"tests.test_config\".'  # noqa: E501\n        == error_messages.pop(0)\n    )\n\n\ndef test_setup_event_loop_is_removed(caplog: pytest.LogCaptureFixture) -> None:\n    config = Config(app=asgi_app)\n    with pytest.raises(\n        AttributeError, match=\"The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\"\n    ):\n        config.setup_event_loop()\n"
  },
  {
    "path": "tests/test_default_headers.py",
    "content": "from __future__ import annotations\n\nimport httpx\nimport pytest\n\nfrom tests.utils import run_server\nfrom uvicorn import Config\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\npytestmark = pytest.mark.anyio\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    assert scope[\"type\"] == \"http\"\n    await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n\nasync def test_default_default_headers(unused_tcp_port: int):\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert response.headers[\"server\"] == \"uvicorn\" and response.headers[\"date\"]\n\n\nasync def test_override_server_header(unused_tcp_port: int):\n    headers: list[tuple[str, str]] = [(\"Server\", \"over-ridden\")]\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, headers=headers, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert response.headers[\"server\"] == \"over-ridden\" and response.headers[\"date\"]\n\n\nasync def test_disable_default_server_header(unused_tcp_port: int):\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, server_header=False, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert \"server\" not in response.headers\n\n\nasync def test_override_server_header_multiple_times(unused_tcp_port: int):\n    headers: list[tuple[str, str]] = [(\"Server\", \"over-ridden\"), (\"Server\", \"another-value\")]\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, headers=headers, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert response.headers[\"server\"] == \"over-ridden, another-value\" and response.headers[\"date\"]\n\n\nasync def test_add_additional_header(unused_tcp_port: int):\n    headers: list[tuple[str, str]] = [(\"X-Additional\", \"new-value\")]\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, headers=headers, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert response.headers[\"x-additional\"] == \"new-value\"\n            assert response.headers[\"server\"] == \"uvicorn\"\n            assert response.headers[\"date\"]\n\n\nasync def test_disable_default_date_header(unused_tcp_port: int):\n    config = Config(app=app, loop=\"asyncio\", limit_max_requests=1, date_header=False, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n            assert \"date\" not in response.headers\n"
  },
  {
    "path": "tests/test_lifespan.py",
    "content": "import asyncio\n\nimport pytest\n\nfrom uvicorn.config import Config\nfrom uvicorn.lifespan.off import LifespanOff\nfrom uvicorn.lifespan.on import LifespanOn\n\n\ndef test_lifespan_on():\n    startup_complete = False\n    shutdown_complete = False\n\n    async def app(scope, receive, send):\n        nonlocal startup_complete, shutdown_complete\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\"\n        startup_complete = True\n        await send({\"type\": \"lifespan.startup.complete\"})\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.shutdown\"\n        shutdown_complete = True\n        await send({\"type\": \"lifespan.shutdown.complete\"})\n\n    async def test():\n        config = Config(app=app, lifespan=\"on\")\n        lifespan = LifespanOn(config)\n\n        assert not startup_complete\n        assert not shutdown_complete\n        await lifespan.startup()\n        assert startup_complete\n        assert not shutdown_complete\n        await lifespan.shutdown()\n        assert startup_complete\n        assert shutdown_complete\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\ndef test_lifespan_off():\n    async def app(scope, receive, send):\n        pass  # pragma: no cover\n\n    async def test():\n        config = Config(app=app, lifespan=\"off\")\n        lifespan = LifespanOff(config)\n\n        await lifespan.startup()\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\ndef test_lifespan_auto():\n    startup_complete = False\n    shutdown_complete = False\n\n    async def app(scope, receive, send):\n        nonlocal startup_complete, shutdown_complete\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\"\n        startup_complete = True\n        await send({\"type\": \"lifespan.startup.complete\"})\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.shutdown\"\n        shutdown_complete = True\n        await send({\"type\": \"lifespan.shutdown.complete\"})\n\n    async def test():\n        config = Config(app=app, lifespan=\"auto\")\n        lifespan = LifespanOn(config)\n\n        assert not startup_complete\n        assert not shutdown_complete\n        await lifespan.startup()\n        assert startup_complete\n        assert not shutdown_complete\n        await lifespan.shutdown()\n        assert startup_complete\n        assert shutdown_complete\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\ndef test_lifespan_auto_with_error():\n    async def app(scope, receive, send):\n        assert scope[\"type\"] == \"http\"\n\n    async def test():\n        config = Config(app=app, lifespan=\"auto\")\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert lifespan.error_occurred\n        assert not lifespan.should_exit\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\ndef test_lifespan_on_with_error():\n    async def app(scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            raise RuntimeError()\n\n    async def test():\n        config = Config(app=app, lifespan=\"on\")\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert lifespan.error_occurred\n        assert lifespan.should_exit\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\n@pytest.mark.parametrize(\"mode\", (\"auto\", \"on\"))\n@pytest.mark.parametrize(\"raise_exception\", (True, False))\ndef test_lifespan_with_failed_startup(mode, raise_exception, caplog):\n    async def app(scope, receive, send):\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\"\n        await send({\"type\": \"lifespan.startup.failed\", \"message\": \"the lifespan event failed\"})\n        if raise_exception:\n            # App should be able to re-raise an exception if startup failed.\n            raise RuntimeError()\n\n    async def test():\n        config = Config(app=app, lifespan=mode)\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert lifespan.startup_failed\n        assert lifespan.error_occurred is raise_exception\n        assert lifespan.should_exit\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n    error_messages = [\n        record.message for record in caplog.records if record.name == \"uvicorn.error\" and record.levelname == \"ERROR\"\n    ]\n    assert \"the lifespan event failed\" in error_messages.pop(0)\n    assert \"Application startup failed. Exiting.\" in error_messages.pop(0)\n\n\ndef test_lifespan_scope_asgi3app():\n    async def asgi3app(scope, receive, send):\n        assert scope == {\n            \"type\": \"lifespan\",\n            \"asgi\": {\"version\": \"3.0\", \"spec_version\": \"2.0\"},\n            \"state\": {},\n        }\n\n    async def test():\n        config = Config(app=asgi3app, lifespan=\"on\")\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert not lifespan.startup_failed\n        assert not lifespan.error_occurred\n        assert not lifespan.should_exit\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\ndef test_lifespan_scope_asgi2app():\n    def asgi2app(scope):\n        assert scope == {\n            \"type\": \"lifespan\",\n            \"asgi\": {\"version\": \"2.0\", \"spec_version\": \"2.0\"},\n            \"state\": {},\n        }\n\n        async def asgi(receive, send):\n            pass\n\n        return asgi\n\n    async def test():\n        config = Config(app=asgi2app, lifespan=\"on\")\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n\n\n@pytest.mark.parametrize(\"mode\", (\"auto\", \"on\"))\n@pytest.mark.parametrize(\"raise_exception\", (True, False))\ndef test_lifespan_with_failed_shutdown(mode, raise_exception, caplog):\n    async def app(scope, receive, send):\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\"\n        await send({\"type\": \"lifespan.startup.complete\"})\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.shutdown\"\n        await send({\"type\": \"lifespan.shutdown.failed\", \"message\": \"the lifespan event failed\"})\n\n        if raise_exception:\n            # App should be able to re-raise an exception if startup failed.\n            raise RuntimeError()\n\n    async def test():\n        config = Config(app=app, lifespan=mode)\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert not lifespan.startup_failed\n        await lifespan.shutdown()\n        assert lifespan.shutdown_failed\n        assert lifespan.error_occurred is raise_exception\n        assert lifespan.should_exit\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    error_messages = [\n        record.message for record in caplog.records if record.name == \"uvicorn.error\" and record.levelname == \"ERROR\"\n    ]\n    assert \"the lifespan event failed\" in error_messages.pop(0)\n    assert \"Application shutdown failed. Exiting.\" in error_messages.pop(0)\n    loop.close()\n\n\ndef test_lifespan_state():\n    async def app(scope, receive, send):\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.startup\"\n        await send({\"type\": \"lifespan.startup.complete\"})\n        scope[\"state\"][\"foo\"] = 123\n        message = await receive()\n        assert message[\"type\"] == \"lifespan.shutdown\"\n        await send({\"type\": \"lifespan.shutdown.complete\"})\n\n    async def test():\n        config = Config(app=app, lifespan=\"on\")\n        lifespan = LifespanOn(config)\n\n        await lifespan.startup()\n        assert lifespan.state == {\"foo\": 123}\n        await lifespan.shutdown()\n\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(test())\n    loop.close()\n"
  },
  {
    "path": "tests/test_main.py",
    "content": "import importlib\nimport inspect\nimport socket\nfrom logging import WARNING\n\nimport httpx\nimport pytest\n\nimport uvicorn.server\nfrom tests.utils import run_server\nfrom uvicorn import Server\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.config import Config\nfrom uvicorn.main import run\n\npytestmark = pytest.mark.anyio\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    assert scope[\"type\"] == \"http\"\n    await send({\"type\": \"http.response.start\", \"status\": 204, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n\ndef _has_ipv6(host: str):\n    sock = None\n    has_ipv6 = False\n    if socket.has_ipv6:\n        try:\n            sock = socket.socket(socket.AF_INET6)\n            sock.bind((host, 0))\n            has_ipv6 = True\n        except Exception:  # pragma: no cover\n            pass\n    if sock:\n        sock.close()\n    return has_ipv6\n\n\n@pytest.mark.parametrize(\n    \"host, url\",\n    [\n        pytest.param(None, \"http://127.0.0.1\", id=\"default\"),\n        pytest.param(\"localhost\", \"http://127.0.0.1\", id=\"hostname\"),\n        pytest.param(\n            \"::1\",\n            \"http://[::1]\",\n            id=\"ipv6\",\n            marks=pytest.mark.skipif(not _has_ipv6(\"::1\"), reason=\"IPV6 not enabled\"),\n        ),\n    ],\n)\nasync def test_run(host, url: str, unused_tcp_port: int):\n    config = Config(app=app, host=host, loop=\"asyncio\", limit_max_requests=1, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"{url}:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\nasync def test_run_multiprocess(unused_tcp_port: int):\n    config = Config(app=app, loop=\"asyncio\", workers=2, limit_max_requests=1, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\nasync def test_run_reload(unused_tcp_port: int):\n    config = Config(app=app, loop=\"asyncio\", reload=True, limit_max_requests=1, port=unused_tcp_port)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            response = await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\ndef test_run_invalid_app_config_combination(caplog: pytest.LogCaptureFixture) -> None:\n    with pytest.raises(SystemExit) as exit_exception:\n        run(app, reload=True)\n    assert exit_exception.value.code == 1\n    assert caplog.records[-1].name == \"uvicorn.error\"\n    assert caplog.records[-1].levelno == WARNING\n    assert caplog.records[-1].message == (\n        \"You must pass the application as an import string to enable 'reload' or 'workers'.\"\n    )\n\n\ndef test_run_startup_failure(caplog: pytest.LogCaptureFixture) -> None:\n    async def app(scope, receive, send):\n        assert scope[\"type\"] == \"lifespan\"\n        message = await receive()\n        if message[\"type\"] == \"lifespan.startup\":\n            raise RuntimeError(\"Startup failed\")\n\n    with pytest.raises(SystemExit) as exit_exception:\n        run(app, lifespan=\"on\")\n    assert exit_exception.value.code == 3\n\n\ndef test_run_match_config_params() -> None:\n    config_params = {\n        key: repr(value)\n        for key, value in inspect.signature(Config.__init__).parameters.items()\n        if key not in (\"self\", \"timeout_notify\", \"callback_notify\")\n    }\n    run_params = {\n        key: repr(value) for key, value in inspect.signature(run).parameters.items() if key not in (\"app_dir\",)\n    }\n    assert config_params == run_params\n\n\nasync def test_exit_on_create_server_with_invalid_host() -> None:\n    with pytest.raises(SystemExit) as exc_info:\n        config = Config(app=app, host=\"illegal_host\")\n        server = Server(config=config)\n        await server.serve()\n    assert exc_info.value.code == 1\n\n\ndef test_deprecated_server_state_from_main() -> None:\n    with pytest.deprecated_call(\n        match=\"uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.\"\n    ):\n        main = importlib.import_module(\"uvicorn.main\")\n        server_state_cls = getattr(main, \"ServerState\")\n    assert server_state_cls is uvicorn.server.ServerState\n"
  },
  {
    "path": "tests/test_server.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport contextlib\nimport contextvars\nimport json\nimport logging\nimport signal\nimport sys\nfrom collections.abc import Callable, Generator\nfrom contextlib import AbstractContextManager\n\nimport httpx\nimport pytest\n\nfrom tests.protocols.test_http import SIMPLE_GET_REQUEST\nfrom tests.utils import run_server\nfrom uvicorn import Server\nfrom uvicorn._types import ASGIApplication, ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.config import Config\nfrom uvicorn.protocols.http.flow_control import HIGH_WATER_LIMIT\nfrom uvicorn.protocols.http.h11_impl import H11Protocol\nfrom uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n\npytestmark = pytest.mark.anyio\n\n\n# asyncio does NOT allow raising in signal handlers, so to detect\n# raised signals raised a mutable `witness` receives the signal\n@contextlib.contextmanager\ndef capture_signal_sync(sig: signal.Signals) -> Generator[list[int], None, None]:\n    \"\"\"Replace `sig` handling with a normal exception via `signal\"\"\"\n    witness: list[int] = []\n    original_handler = signal.signal(sig, lambda signum, frame: witness.append(signum))\n    yield witness\n    signal.signal(sig, original_handler)\n\n\n@contextlib.contextmanager\ndef capture_signal_async(sig: signal.Signals) -> Generator[list[int], None, None]:  # pragma: py-win32\n    \"\"\"Replace `sig` handling with a normal exception via `asyncio\"\"\"\n    witness: list[int] = []\n    original_handler = signal.getsignal(sig)\n    asyncio.get_running_loop().add_signal_handler(sig, witness.append, sig)\n    yield witness\n    signal.signal(sig, original_handler)\n\n\nasync def dummy_app(scope, receive, send):  # pragma: py-win32\n    pass\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    assert scope[\"type\"] == \"http\"\n    await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n\nif sys.platform == \"win32\":  # pragma: py-not-win32\n    signals = [signal.SIGBREAK]\n    signal_captures = [capture_signal_sync]\nelse:  # pragma: py-win32\n    signals = [signal.SIGTERM, signal.SIGINT]\n    signal_captures = [capture_signal_sync, capture_signal_async]\n\n\n@pytest.mark.parametrize(\"exception_signal\", signals)\n@pytest.mark.parametrize(\"capture_signal\", signal_captures)\nasync def test_server_interrupt(\n    exception_signal: signal.Signals,\n    capture_signal: Callable[[signal.Signals], AbstractContextManager[None]],\n    unused_tcp_port: int,\n):  # pragma: py-win32\n    \"\"\"Test interrupting a Server that is run explicitly inside asyncio\"\"\"\n\n    async def interrupt_running(srv: Server):\n        while not srv.started:\n            await asyncio.sleep(0.01)\n        signal.raise_signal(exception_signal)\n\n    server = Server(Config(app=dummy_app, loop=\"asyncio\", port=unused_tcp_port))\n    asyncio.create_task(interrupt_running(server))\n    with capture_signal(exception_signal) as witness:\n        await server.serve()\n    assert witness\n    # set by the server's graceful exit handler\n    assert server.should_exit\n\n\nasync def test_shutdown_on_early_exit_during_startup(unused_tcp_port: int):\n    \"\"\"Test that lifespan.shutdown is called even when should_exit is set during startup.\"\"\"\n    startup_complete = False\n    shutdown_complete = False\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        nonlocal startup_complete, shutdown_complete\n        if scope[\"type\"] == \"lifespan\":\n            while True:\n                message = await receive()\n                if message[\"type\"] == \"lifespan.startup\":\n                    await asyncio.sleep(0.5)\n                    await send({\"type\": \"lifespan.startup.complete\"})\n                    startup_complete = True\n                elif message[\"type\"] == \"lifespan.shutdown\":\n                    await send({\"type\": \"lifespan.shutdown.complete\"})\n                    shutdown_complete = True\n                    return\n\n    config = Config(app=app, lifespan=\"on\", port=unused_tcp_port)\n    server = Server(config=config)\n\n    # Simulate a reload signal arriving during startup:\n    # set should_exit before the 0.5s startup sleep finishes.\n    async def set_exit():\n        await asyncio.sleep(0.2)\n        server.should_exit = True\n\n    asyncio.create_task(set_exit())\n    await server.serve()\n\n    assert startup_complete\n    assert shutdown_complete, \"lifespan.shutdown was not called despite startup completing\"\n\n\nasync def test_request_than_limit_max_requests_warn_log(\n    unused_tcp_port: int, http_protocol_cls: type[H11Protocol | HttpToolsProtocol], caplog: pytest.LogCaptureFixture\n):\n    caplog.set_level(logging.INFO, logger=\"uvicorn.error\")\n    config = Config(app=app, limit_max_requests=1, port=unused_tcp_port, http=http_protocol_cls)\n    async with run_server(config):\n        async with httpx.AsyncClient() as client:\n            tasks = [client.get(f\"http://127.0.0.1:{unused_tcp_port}\") for _ in range(2)]\n            responses = await asyncio.gather(*tasks)\n            assert len(responses) == 2\n    assert \"Maximum request limit of 1 exceeded. Terminating process.\" in caplog.text\n\n\nasync def test_limit_max_requests_jitter(\n    unused_tcp_port: int, http_protocol_cls: type[H11Protocol | HttpToolsProtocol], caplog: pytest.LogCaptureFixture\n):\n    caplog.set_level(logging.INFO, logger=\"uvicorn.error\")\n    config = Config(\n        app=app, limit_max_requests=1, limit_max_requests_jitter=2, port=unused_tcp_port, http=http_protocol_cls\n    )\n    server = Server(config=config)\n    limit = server.limit_max_requests\n    assert limit is not None\n    assert 1 <= limit <= 3\n    task = asyncio.create_task(server.serve())\n    while not server.started:\n        await asyncio.sleep(0.01)\n    async with httpx.AsyncClient() as client:\n        for _ in range(limit + 1):\n            await client.get(f\"http://127.0.0.1:{unused_tcp_port}\")\n    await task\n    assert f\"Maximum request limit of {limit} exceeded. Terminating process.\" in caplog.text\n\n\n@contextlib.asynccontextmanager\nasync def server(*, app: ASGIApplication, port: int, http_protocol_cls: type[H11Protocol | HttpToolsProtocol]):\n    config = Config(app=app, port=port, loop=\"asyncio\", http=http_protocol_cls)\n    server = Server(config=config)\n    task = asyncio.create_task(server.serve())\n\n    while not server.started:\n        await asyncio.sleep(0.01)\n\n    reader, writer = await asyncio.open_connection(\"127.0.0.1\", port)\n\n    async def extract_json_body(request: bytes):\n        writer.write(request)\n        await writer.drain()\n\n        status, *headers = (await reader.readuntil(b\"\\r\\n\\r\\n\")).split(b\"\\r\\n\")[:-2]\n        assert status == b\"HTTP/1.1 200 OK\"\n\n        content_length = next(int(h.split(b\":\", 1)[1]) for h in headers if h.lower().startswith(b\"content-length:\"))\n        return json.loads(await reader.readexactly(content_length))\n\n    try:\n        yield extract_json_body\n    finally:\n        writer.close()\n        await writer.wait_closed()\n        server.should_exit = True\n        await task\n\n\nasync def test_no_contextvars_pollution_asyncio(\n    http_protocol_cls: type[H11Protocol | HttpToolsProtocol], unused_tcp_port: int\n):\n    \"\"\"Non-regression test for https://github.com/encode/uvicorn/issues/2167.\"\"\"\n    default_contextvars = {c.name for c in contextvars.copy_context().keys()}\n    ctx: contextvars.ContextVar[str] = contextvars.ContextVar(\"ctx\")\n\n    async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):\n        assert scope[\"type\"] == \"http\"\n\n        # initial context should be empty\n        initial_context = {\n            n: v for c, v in contextvars.copy_context().items() if (n := c.name) not in default_contextvars\n        }\n        # set any contextvar before the body is read\n        ctx.set(scope[\"path\"])\n\n        while True:\n            message = await receive()\n            assert message[\"type\"] == \"http.request\"\n            if not message[\"more_body\"]:\n                break\n\n        # return the initial context for empty assertion\n        body = json.dumps(initial_context).encode(\"utf-8\")\n        headers = [(b\"content-type\", b\"application/json\"), (b\"content-length\", str(len(body)).encode(\"utf-8\"))]\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": headers})\n        await send({\"type\": \"http.response.body\", \"body\": body})\n\n    # body has to be larger than HIGH_WATER_LIMIT to trigger a reading pause on the main thread\n    # and a resumption inside the ASGI task\n    large_body = b\"a\" * (HIGH_WATER_LIMIT + 1)\n    large_request = b\"\\r\\n\".join(\n        [\n            b\"POST /large-body HTTP/1.1\",\n            b\"Host: example.org\",\n            b\"Content-Type: application/octet-stream\",\n            f\"Content-Length: {len(large_body)}\".encode(),\n            b\"\",\n            large_body,\n        ]\n    )\n\n    async with server(app=app, http_protocol_cls=http_protocol_cls, port=unused_tcp_port) as extract_json_body:\n        assert await extract_json_body(large_request) == {}\n        assert await extract_json_body(SIMPLE_GET_REQUEST) == {}\n"
  },
  {
    "path": "tests/test_ssl.py",
    "content": "import httpx\nimport pytest\n\nfrom tests.utils import run_server\nfrom uvicorn.config import Config\n\n\nasync def app(scope, receive, send):\n    assert scope[\"type\"] == \"http\"\n    await send({\"type\": \"http.response.start\", \"status\": 204, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n\n@pytest.mark.anyio\nasync def test_run(\n    tls_ca_ssl_context,\n    tls_certificate_server_cert_path,\n    tls_certificate_private_key_path,\n    tls_ca_certificate_pem_path,\n    unused_tcp_port: int,\n):\n    config = Config(\n        app=app,\n        loop=\"asyncio\",\n        limit_max_requests=1,\n        ssl_keyfile=tls_certificate_private_key_path,\n        ssl_certfile=tls_certificate_server_cert_path,\n        ssl_ca_certs=tls_ca_certificate_pem_path,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with httpx.AsyncClient(verify=tls_ca_ssl_context) as client:\n            response = await client.get(f\"https://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\n@pytest.mark.anyio\nasync def test_run_chain(\n    tls_ca_ssl_context,\n    tls_certificate_key_and_chain_path,\n    tls_ca_certificate_pem_path,\n    unused_tcp_port: int,\n):\n    config = Config(\n        app=app,\n        loop=\"asyncio\",\n        limit_max_requests=1,\n        ssl_certfile=tls_certificate_key_and_chain_path,\n        ssl_ca_certs=tls_ca_certificate_pem_path,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with httpx.AsyncClient(verify=tls_ca_ssl_context) as client:\n            response = await client.get(f\"https://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\n@pytest.mark.anyio\nasync def test_run_chain_only(tls_ca_ssl_context, tls_certificate_key_and_chain_path, unused_tcp_port: int):\n    config = Config(\n        app=app,\n        loop=\"asyncio\",\n        limit_max_requests=1,\n        ssl_certfile=tls_certificate_key_and_chain_path,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with httpx.AsyncClient(verify=tls_ca_ssl_context) as client:\n            response = await client.get(f\"https://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n\n\n@pytest.mark.anyio\nasync def test_run_password(\n    tls_ca_ssl_context,\n    tls_certificate_server_cert_path,\n    tls_ca_certificate_pem_path,\n    tls_certificate_private_key_encrypted_path,\n    unused_tcp_port: int,\n):\n    config = Config(\n        app=app,\n        loop=\"asyncio\",\n        limit_max_requests=1,\n        ssl_keyfile=tls_certificate_private_key_encrypted_path,\n        ssl_certfile=tls_certificate_server_cert_path,\n        ssl_keyfile_password=\"uvicorn password for the win\",\n        ssl_ca_certs=tls_ca_certificate_pem_path,\n        port=unused_tcp_port,\n    )\n    async with run_server(config):\n        async with httpx.AsyncClient(verify=tls_ca_ssl_context) as client:\n            response = await client.get(f\"https://127.0.0.1:{unused_tcp_port}\")\n    assert response.status_code == 204\n"
  },
  {
    "path": "tests/test_subprocess.py",
    "content": "from __future__ import annotations\n\nimport socket\nfrom unittest.mock import patch\n\nfrom uvicorn._subprocess import SpawnProcess, get_subprocess, subprocess_started\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\nfrom uvicorn.config import Config\n\n\ndef server_run(sockets: list[socket.socket]):  # pragma: no cover\n    ...\n\n\nasync def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:  # pragma: no cover\n    ...\n\n\ndef test_get_subprocess() -> None:\n    fdsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    fd = fdsock.fileno()\n    config = Config(app=app, fd=fd)\n    config.load()\n\n    process = get_subprocess(config, server_run, [fdsock])\n    assert isinstance(process, SpawnProcess)\n\n    fdsock.close()\n\n\ndef test_subprocess_started() -> None:\n    fdsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    fd = fdsock.fileno()\n    config = Config(app=app, fd=fd)\n    config.load()\n\n    with patch(\"tests.test_subprocess.server_run\") as mock_run:\n        with patch.object(config, \"configure_logging\") as mock_config_logging:\n            subprocess_started(config, server_run, [fdsock], None)\n            mock_run.assert_called_once()\n            mock_config_logging.assert_called_once()\n\n    fdsock.close()\n"
  },
  {
    "path": "tests/utils.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport os\nimport signal\nimport sys\nfrom collections.abc import AsyncIterator\nfrom contextlib import asynccontextmanager, contextmanager\nfrom pathlib import Path\nfrom socket import socket\n\nfrom uvicorn import Config, Server\n\n\n@asynccontextmanager\nasync def run_server(config: Config, sockets: list[socket] | None = None) -> AsyncIterator[Server]:\n    server = Server(config=config)\n    task = asyncio.create_task(server.serve(sockets=sockets))\n    while not server.started:\n        await asyncio.sleep(0.05)\n    try:\n        yield server\n    finally:\n        await server.shutdown()\n        task.cancel()\n\n\n@contextmanager\ndef assert_signal(sig: signal.Signals):\n    \"\"\"Check that a signal was received and handled in a block\"\"\"\n    seen: set[int] = set()\n    prev_handler = signal.signal(sig, lambda num, frame: seen.add(num))\n    try:\n        yield\n        assert sig in seen, f\"process signal {signal.Signals(sig)!r} was not received or handled\"\n    finally:\n        signal.signal(sig, prev_handler)\n\n\n@contextmanager\ndef as_cwd(path: Path):\n    \"\"\"Changes working directory and returns to previous on exit.\"\"\"\n    prev_cwd = Path.cwd()\n    os.chdir(path)\n    try:\n        yield\n    finally:\n        os.chdir(prev_cwd)\n\n\ndef get_asyncio_default_loop_per_os() -> type[asyncio.AbstractEventLoop]:\n    \"\"\"Get the default asyncio loop per OS.\"\"\"\n    if sys.platform == \"win32\":\n        return asyncio.ProactorEventLoop  # type: ignore  # pragma: nocover\n    else:\n        return asyncio.SelectorEventLoop  # pragma: nocover\n"
  },
  {
    "path": "uvicorn/__init__.py",
    "content": "from uvicorn.config import Config\nfrom uvicorn.main import Server, main, run\n\n__version__ = \"0.42.0\"\n__all__ = [\"main\", \"run\", \"Config\", \"Server\"]\n"
  },
  {
    "path": "uvicorn/__main__.py",
    "content": "import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.main()\n"
  },
  {
    "path": "uvicorn/_compat.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable, Coroutine\nfrom typing import Any, TypeVar\n\n__all__ = [\"asyncio_run\", \"iscoroutinefunction\"]\n\nif sys.version_info >= (3, 14):\n    from inspect import iscoroutinefunction\nelse:\n    from asyncio import iscoroutinefunction\n\n_T = TypeVar(\"_T\")\n\nif sys.version_info >= (3, 12):\n    asyncio_run = asyncio.run\nelif sys.version_info >= (3, 11):\n\n    def asyncio_run(\n        main: Coroutine[Any, Any, _T],\n        *,\n        debug: bool = False,\n        loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,\n    ) -> _T:\n        # asyncio.run from Python 3.12\n        # https://docs.python.org/3/license.html#psf-license\n        with asyncio.Runner(debug=debug, loop_factory=loop_factory) as runner:\n            return runner.run(main)\n\nelse:\n    # modified version of asyncio.run from Python 3.10 to add loop_factory kwarg\n    # https://docs.python.org/3/license.html#psf-license\n    def asyncio_run(\n        main: Coroutine[Any, Any, _T],\n        *,\n        debug: bool = False,\n        loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,\n    ) -> _T:\n        try:\n            asyncio.get_running_loop()\n        except RuntimeError:\n            pass\n        else:\n            raise RuntimeError(\"asyncio.run() cannot be called from a running event loop\")\n\n        if not asyncio.iscoroutine(main):\n            raise ValueError(f\"a coroutine was expected, got {main!r}\")\n\n        if loop_factory is None:\n            loop = asyncio.new_event_loop()\n        else:\n            loop = loop_factory()\n        try:\n            if loop_factory is None:\n                asyncio.set_event_loop(loop)\n            if debug is not None:\n                loop.set_debug(debug)\n            return loop.run_until_complete(main)\n        finally:\n            try:\n                _cancel_all_tasks(loop)\n                loop.run_until_complete(loop.shutdown_asyncgens())\n                loop.run_until_complete(loop.shutdown_default_executor())\n            finally:\n                if loop_factory is None:\n                    asyncio.set_event_loop(None)\n                loop.close()\n\n    def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None:\n        to_cancel = asyncio.all_tasks(loop)\n        if not to_cancel:\n            return\n\n        for task in to_cancel:\n            task.cancel()\n\n        loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))\n\n        for task in to_cancel:\n            if task.cancelled():\n                continue\n            if task.exception() is not None:\n                loop.call_exception_handler(\n                    {\n                        \"message\": \"unhandled exception during asyncio.run() shutdown\",\n                        \"exception\": task.exception(),\n                        \"task\": task,\n                    }\n                )\n"
  },
  {
    "path": "uvicorn/_subprocess.py",
    "content": "\"\"\"\nSome light wrappers around Python's multiprocessing, to deal with cleanly\nstarting child processes.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport multiprocessing\nimport os\nimport sys\nfrom collections.abc import Callable\nfrom multiprocessing.context import SpawnProcess\nfrom socket import socket\n\nfrom uvicorn.config import Config\n\nmultiprocessing.allow_connection_pickling()\nspawn = multiprocessing.get_context(\"spawn\")\n\n\ndef get_subprocess(\n    config: Config,\n    target: Callable[..., None],\n    sockets: list[socket],\n) -> SpawnProcess:\n    \"\"\"\n    Called in the parent process, to instantiate a new child process instance.\n    The child is not yet started at this point.\n\n    * config - The Uvicorn configuration instance.\n    * target - A callable that accepts a list of sockets. In practice this will\n               be the `Server.run()` method.\n    * sockets - A list of sockets to pass to the server. Sockets are bound once\n                by the parent process, and then passed to the child processes.\n    \"\"\"\n    # We pass across the stdin fileno, and reopen it in the child process.\n    # This is required for some debugging environments.\n    try:\n        stdin_fileno = sys.stdin.fileno()\n    # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.\n    except (AttributeError, OSError):\n        stdin_fileno = None\n\n    kwargs = {\n        \"config\": config,\n        \"target\": target,\n        \"sockets\": sockets,\n        \"stdin_fileno\": stdin_fileno,\n    }\n\n    return spawn.Process(target=subprocess_started, kwargs=kwargs)\n\n\ndef subprocess_started(\n    config: Config,\n    target: Callable[..., None],\n    sockets: list[socket],\n    stdin_fileno: int | None,\n) -> None:\n    \"\"\"\n    Called when the child process starts.\n\n    * config - The Uvicorn configuration instance.\n    * target - A callable that accepts a list of sockets. In practice this will\n               be the `Server.run()` method.\n    * sockets - A list of sockets to pass to the server. Sockets are bound once\n                by the parent process, and then passed to the child processes.\n    * stdin_fileno - The file number of sys.stdin, so that it can be reattached\n                     to the child process.\n    \"\"\"\n    # Re-open stdin.\n    if stdin_fileno is not None:\n        sys.stdin = os.fdopen(stdin_fileno)  # pragma: full coverage\n\n    # Logging needs to be setup again for each child.\n    config.configure_logging()\n\n    try:\n        # Now we can call into `Server.run(sockets=sockets)`\n        target(sockets=sockets)\n    except KeyboardInterrupt:  # pragma: no cover\n        # suppress the exception to avoid a traceback from subprocess.Popen\n        # the parent already expects us to end, so no vital information is lost\n        pass\n"
  },
  {
    "path": "uvicorn/_types.py",
    "content": "\"\"\"\nCopyright (c) Django Software Foundation and individual contributors.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice,\n       this list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright\n       notice, this list of conditions and the following disclaimer in the\n       documentation and/or other materials provided with the distribution.\n\n    3. Neither the name of Django nor the names of its contributors may be used\n       to endorse or promote products derived from this software without\n       specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nimport types\nfrom collections.abc import Awaitable, Callable, Iterable, MutableMapping\nfrom typing import Any, Literal, Protocol, TypedDict\n\nif sys.version_info >= (3, 11):  # pragma: py-lt-311\n    from typing import NotRequired\nelse:  # pragma: py-gte-311\n    from typing_extensions import NotRequired\n\n# WSGI\nEnviron = MutableMapping[str, Any]\nExcInfo = tuple[type[BaseException], BaseException, types.TracebackType | None]\nStartResponse = Callable[[str, Iterable[tuple[str, str]], ExcInfo | None], None]\nWSGIApp = Callable[[Environ, StartResponse], Iterable[bytes] | BaseException]\n\n\n# ASGI\nclass ASGIVersions(TypedDict):\n    spec_version: str\n    version: Literal[\"2.0\"] | Literal[\"3.0\"]\n\n\nclass HTTPScope(TypedDict):\n    type: Literal[\"http\"]\n    asgi: ASGIVersions\n    http_version: str\n    method: str\n    scheme: str\n    path: str\n    raw_path: bytes\n    query_string: bytes\n    root_path: str\n    headers: Iterable[tuple[bytes, bytes]]\n    client: tuple[str, int] | None\n    server: tuple[str, int | None] | None\n    state: NotRequired[dict[str, Any]]\n    extensions: NotRequired[dict[str, dict[object, object]]]\n\n\nclass WebSocketScope(TypedDict):\n    type: Literal[\"websocket\"]\n    asgi: ASGIVersions\n    http_version: str\n    scheme: str\n    path: str\n    raw_path: bytes\n    query_string: bytes\n    root_path: str\n    headers: Iterable[tuple[bytes, bytes]]\n    client: tuple[str, int] | None\n    server: tuple[str, int | None] | None\n    subprotocols: Iterable[str]\n    state: NotRequired[dict[str, Any]]\n    extensions: NotRequired[dict[str, dict[object, object]]]\n\n\nclass LifespanScope(TypedDict):\n    type: Literal[\"lifespan\"]\n    asgi: ASGIVersions\n    state: NotRequired[dict[str, Any]]\n\n\nWWWScope = HTTPScope | WebSocketScope\nScope = HTTPScope | WebSocketScope | LifespanScope\n\n\nclass HTTPRequestEvent(TypedDict):\n    type: Literal[\"http.request\"]\n    body: bytes\n    more_body: bool\n\n\nclass HTTPResponseDebugEvent(TypedDict):\n    type: Literal[\"http.response.debug\"]\n    info: dict[str, object]\n\n\nclass HTTPResponseStartEvent(TypedDict):\n    type: Literal[\"http.response.start\"]\n    status: int\n    headers: NotRequired[Iterable[tuple[bytes, bytes]]]\n    trailers: NotRequired[bool]\n\n\nclass HTTPResponseBodyEvent(TypedDict):\n    type: Literal[\"http.response.body\"]\n    body: bytes\n    more_body: NotRequired[bool]\n\n\nclass HTTPResponseTrailersEvent(TypedDict):\n    type: Literal[\"http.response.trailers\"]\n    headers: Iterable[tuple[bytes, bytes]]\n    more_trailers: bool\n\n\nclass HTTPServerPushEvent(TypedDict):\n    type: Literal[\"http.response.push\"]\n    path: str\n    headers: Iterable[tuple[bytes, bytes]]\n\n\nclass HTTPDisconnectEvent(TypedDict):\n    type: Literal[\"http.disconnect\"]\n\n\nclass WebSocketConnectEvent(TypedDict):\n    type: Literal[\"websocket.connect\"]\n\n\nclass WebSocketAcceptEvent(TypedDict):\n    type: Literal[\"websocket.accept\"]\n    subprotocol: NotRequired[str | None]\n    headers: NotRequired[Iterable[tuple[bytes, bytes]]]\n\n\nclass _WebSocketReceiveEventBytes(TypedDict):\n    type: Literal[\"websocket.receive\"]\n    bytes: bytes\n    text: NotRequired[None]\n\n\nclass _WebSocketReceiveEventText(TypedDict):\n    type: Literal[\"websocket.receive\"]\n    bytes: NotRequired[None]\n    text: str\n\n\nWebSocketReceiveEvent = _WebSocketReceiveEventBytes | _WebSocketReceiveEventText\n\n\nclass _WebSocketSendEventBytes(TypedDict):\n    type: Literal[\"websocket.send\"]\n    bytes: bytes\n    text: NotRequired[None]\n\n\nclass _WebSocketSendEventText(TypedDict):\n    type: Literal[\"websocket.send\"]\n    bytes: NotRequired[None]\n    text: str\n\n\nWebSocketSendEvent = _WebSocketSendEventBytes | _WebSocketSendEventText\n\n\nclass WebSocketResponseStartEvent(TypedDict):\n    type: Literal[\"websocket.http.response.start\"]\n    status: int\n    headers: Iterable[tuple[bytes, bytes]]\n\n\nclass WebSocketResponseBodyEvent(TypedDict):\n    type: Literal[\"websocket.http.response.body\"]\n    body: bytes\n    more_body: NotRequired[bool]\n\n\nclass WebSocketDisconnectEvent(TypedDict):\n    type: Literal[\"websocket.disconnect\"]\n    code: int\n    reason: NotRequired[str | None]\n\n\nclass WebSocketCloseEvent(TypedDict):\n    type: Literal[\"websocket.close\"]\n    code: NotRequired[int]\n    reason: NotRequired[str | None]\n\n\nclass LifespanStartupEvent(TypedDict):\n    type: Literal[\"lifespan.startup\"]\n\n\nclass LifespanShutdownEvent(TypedDict):\n    type: Literal[\"lifespan.shutdown\"]\n\n\nclass LifespanStartupCompleteEvent(TypedDict):\n    type: Literal[\"lifespan.startup.complete\"]\n\n\nclass LifespanStartupFailedEvent(TypedDict):\n    type: Literal[\"lifespan.startup.failed\"]\n    message: str\n\n\nclass LifespanShutdownCompleteEvent(TypedDict):\n    type: Literal[\"lifespan.shutdown.complete\"]\n\n\nclass LifespanShutdownFailedEvent(TypedDict):\n    type: Literal[\"lifespan.shutdown.failed\"]\n    message: str\n\n\nWebSocketEvent = WebSocketReceiveEvent | WebSocketDisconnectEvent | WebSocketConnectEvent\n\n\nASGIReceiveEvent = (\n    HTTPRequestEvent\n    | HTTPDisconnectEvent\n    | WebSocketConnectEvent\n    | WebSocketReceiveEvent\n    | WebSocketDisconnectEvent\n    | LifespanStartupEvent\n    | LifespanShutdownEvent\n)\n\n\nASGISendEvent = (\n    HTTPResponseStartEvent\n    | HTTPResponseBodyEvent\n    | HTTPResponseTrailersEvent\n    | HTTPServerPushEvent\n    | HTTPDisconnectEvent\n    | WebSocketAcceptEvent\n    | WebSocketSendEvent\n    | WebSocketResponseStartEvent\n    | WebSocketResponseBodyEvent\n    | WebSocketCloseEvent\n    | LifespanStartupCompleteEvent\n    | LifespanStartupFailedEvent\n    | LifespanShutdownCompleteEvent\n    | LifespanShutdownFailedEvent\n)\n\n\nASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]\nASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]\n\n\nclass ASGI2Protocol(Protocol):\n    def __init__(self, scope: Scope) -> None: ...  # pragma: no cover\n\n    async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ...  # pragma: no cover\n\n\nASGI2Application = type[ASGI2Protocol]\nASGI3Application = Callable[[Scope, ASGIReceiveCallable, ASGISendCallable], Awaitable[None]]\nASGIApplication = ASGI2Application | ASGI3Application\n"
  },
  {
    "path": "uvicorn/config.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport inspect\nimport json\nimport logging\nimport logging.config\nimport os\nimport socket\nimport ssl\nimport sys\nfrom collections.abc import Awaitable, Callable\nfrom configparser import RawConfigParser\nfrom pathlib import Path\nfrom typing import IO, Any, Literal\n\nimport click\n\nfrom uvicorn._compat import iscoroutinefunction\nfrom uvicorn._types import ASGIApplication\nfrom uvicorn.importer import ImportFromStringError, import_from_string\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.middleware.asgi2 import ASGI2Middleware\nfrom uvicorn.middleware.message_logger import MessageLoggerMiddleware\nfrom uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware\nfrom uvicorn.middleware.wsgi import WSGIMiddleware\n\nHTTPProtocolType = Literal[\"auto\", \"h11\", \"httptools\"]\nWSProtocolType = Literal[\"auto\", \"none\", \"websockets\", \"websockets-sansio\", \"wsproto\"]\nLifespanType = Literal[\"auto\", \"on\", \"off\"]\nLoopFactoryType = Literal[\"none\", \"auto\", \"asyncio\", \"uvloop\"]\nInterfaceType = Literal[\"auto\", \"asgi3\", \"asgi2\", \"wsgi\"]\n\nLOG_LEVELS: dict[str, int] = {\n    \"critical\": logging.CRITICAL,\n    \"error\": logging.ERROR,\n    \"warning\": logging.WARNING,\n    \"info\": logging.INFO,\n    \"debug\": logging.DEBUG,\n    \"trace\": TRACE_LOG_LEVEL,\n}\nHTTP_PROTOCOLS: dict[str, str] = {\n    \"auto\": \"uvicorn.protocols.http.auto:AutoHTTPProtocol\",\n    \"h11\": \"uvicorn.protocols.http.h11_impl:H11Protocol\",\n    \"httptools\": \"uvicorn.protocols.http.httptools_impl:HttpToolsProtocol\",\n}\nWS_PROTOCOLS: dict[str, str | None] = {\n    \"auto\": \"uvicorn.protocols.websockets.auto:AutoWebSocketsProtocol\",\n    \"none\": None,\n    \"websockets\": \"uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol\",\n    \"websockets-sansio\": \"uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol\",\n    \"wsproto\": \"uvicorn.protocols.websockets.wsproto_impl:WSProtocol\",\n}\nLIFESPAN: dict[str, str] = {\n    \"auto\": \"uvicorn.lifespan.on:LifespanOn\",\n    \"on\": \"uvicorn.lifespan.on:LifespanOn\",\n    \"off\": \"uvicorn.lifespan.off:LifespanOff\",\n}\nLOOP_FACTORIES: dict[str, str | None] = {\n    \"none\": None,\n    \"auto\": \"uvicorn.loops.auto:auto_loop_factory\",\n    \"asyncio\": \"uvicorn.loops.asyncio:asyncio_loop_factory\",\n    \"uvloop\": \"uvicorn.loops.uvloop:uvloop_loop_factory\",\n}\nINTERFACES: list[InterfaceType] = [\"auto\", \"asgi3\", \"asgi2\", \"wsgi\"]\n\nSSL_PROTOCOL_VERSION: int = ssl.PROTOCOL_TLS_SERVER\n\nLOGGING_CONFIG: dict[str, Any] = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"default\": {\n            \"()\": \"uvicorn.logging.DefaultFormatter\",\n            \"fmt\": \"%(levelprefix)s %(message)s\",\n            \"use_colors\": None,\n        },\n        \"access\": {\n            \"()\": \"uvicorn.logging.AccessFormatter\",\n            \"fmt\": '%(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s',  # noqa: E501\n        },\n    },\n    \"handlers\": {\n        \"default\": {\n            \"formatter\": \"default\",\n            \"class\": \"logging.StreamHandler\",\n            \"stream\": \"ext://sys.stderr\",\n        },\n        \"access\": {\n            \"formatter\": \"access\",\n            \"class\": \"logging.StreamHandler\",\n            \"stream\": \"ext://sys.stdout\",\n        },\n    },\n    \"loggers\": {\n        \"uvicorn\": {\"handlers\": [\"default\"], \"level\": \"INFO\", \"propagate\": False},\n        \"uvicorn.error\": {\"level\": \"INFO\"},\n        \"uvicorn.access\": {\"handlers\": [\"access\"], \"level\": \"INFO\", \"propagate\": False},\n    },\n}\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\ndef create_ssl_context(\n    certfile: str | os.PathLike[str],\n    keyfile: str | os.PathLike[str] | None,\n    password: str | None,\n    ssl_version: int,\n    cert_reqs: int,\n    ca_certs: str | os.PathLike[str] | None,\n    ciphers: str | None,\n) -> ssl.SSLContext:\n    ctx = ssl.SSLContext(ssl_version)\n    get_password = (lambda: password) if password else None\n    ctx.load_cert_chain(certfile, keyfile, get_password)\n    ctx.verify_mode = ssl.VerifyMode(cert_reqs)\n    if ca_certs:\n        ctx.load_verify_locations(ca_certs)\n    if ciphers:\n        ctx.set_ciphers(ciphers)\n    return ctx\n\n\ndef is_dir(path: Path) -> bool:\n    try:\n        if not path.is_absolute():\n            path = path.resolve()\n        return path.is_dir()\n    except OSError:  # pragma: full coverage\n        return False\n\n\ndef resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]:\n    directories: list[Path] = list(set(map(Path, directories_list.copy())))\n    patterns: list[str] = patterns_list.copy()\n\n    current_working_directory = Path.cwd()\n    for pattern in patterns_list:\n        # Special case for the .* pattern, otherwise this would only match\n        # hidden directories which is probably undesired\n        if pattern == \".*\":\n            continue  # pragma: py-not-linux\n        patterns.append(pattern)\n        if is_dir(Path(pattern)):\n            directories.append(Path(pattern))\n        else:\n            for match in current_working_directory.glob(pattern):\n                if is_dir(match):\n                    directories.append(match)\n\n    directories = list(set(directories))\n    directories = list(map(Path, directories))\n    directories = list(map(lambda x: x.resolve(), directories))\n    directories = list({reload_path for reload_path in directories if is_dir(reload_path)})\n\n    children = []\n    for j in range(len(directories)):\n        for k in range(j + 1, len(directories)):  # pragma: full coverage\n            if directories[j] in directories[k].parents:\n                children.append(directories[k])\n            elif directories[k] in directories[j].parents:\n                children.append(directories[j])\n\n    directories = list(set(directories).difference(set(children)))\n\n    return list(set(patterns)), directories\n\n\ndef _normalize_dirs(dirs: list[str] | str | None) -> list[str]:\n    if dirs is None:\n        return []\n    if isinstance(dirs, str):\n        return [dirs]\n    return list(set(dirs))\n\n\nclass Config:\n    def __init__(\n        self,\n        app: ASGIApplication | Callable[..., Any] | str,\n        host: str = \"127.0.0.1\",\n        port: int = 8000,\n        uds: str | None = None,\n        fd: int | None = None,\n        loop: LoopFactoryType | str = \"auto\",\n        http: type[asyncio.Protocol] | HTTPProtocolType | str = \"auto\",\n        ws: type[asyncio.Protocol] | WSProtocolType | str = \"auto\",\n        ws_max_size: int = 16 * 1024 * 1024,\n        ws_max_queue: int = 32,\n        ws_ping_interval: float | None = 20.0,\n        ws_ping_timeout: float | None = 20.0,\n        ws_per_message_deflate: bool = True,\n        lifespan: LifespanType = \"auto\",\n        env_file: str | os.PathLike[str] | None = None,\n        log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = LOGGING_CONFIG,\n        log_level: str | int | None = None,\n        access_log: bool = True,\n        use_colors: bool | None = None,\n        interface: InterfaceType = \"auto\",\n        reload: bool = False,\n        reload_dirs: list[str] | str | None = None,\n        reload_delay: float = 0.25,\n        reload_includes: list[str] | str | None = None,\n        reload_excludes: list[str] | str | None = None,\n        workers: int | None = None,\n        proxy_headers: bool = True,\n        server_header: bool = True,\n        date_header: bool = True,\n        forwarded_allow_ips: list[str] | str | None = None,\n        root_path: str = \"\",\n        limit_concurrency: int | None = None,\n        limit_max_requests: int | None = None,\n        limit_max_requests_jitter: int = 0,\n        backlog: int = 2048,\n        timeout_keep_alive: int = 5,\n        timeout_notify: int = 30,\n        timeout_graceful_shutdown: int | None = None,\n        timeout_worker_healthcheck: int = 5,\n        callback_notify: Callable[..., Awaitable[None]] | None = None,\n        ssl_keyfile: str | os.PathLike[str] | None = None,\n        ssl_certfile: str | os.PathLike[str] | None = None,\n        ssl_keyfile_password: str | None = None,\n        ssl_version: int = SSL_PROTOCOL_VERSION,\n        ssl_cert_reqs: int = ssl.CERT_NONE,\n        ssl_ca_certs: str | os.PathLike[str] | None = None,\n        ssl_ciphers: str = \"TLSv1\",\n        headers: list[tuple[str, str]] | None = None,\n        factory: bool = False,\n        h11_max_incomplete_event_size: int | None = None,\n    ):\n        self.app = app\n        self.host = host\n        self.port = port\n        self.uds = uds\n        self.fd = fd\n        self.loop = loop\n        self.http = http\n        self.ws = ws\n        self.ws_max_size = ws_max_size\n        self.ws_max_queue = ws_max_queue\n        self.ws_ping_interval = ws_ping_interval\n        self.ws_ping_timeout = ws_ping_timeout\n        self.ws_per_message_deflate = ws_per_message_deflate\n        self.lifespan = lifespan\n        self.log_config = log_config\n        self.log_level = log_level\n        self.access_log = access_log\n        self.use_colors = use_colors\n        self.interface = interface\n        self.reload = reload\n        self.reload_delay = reload_delay\n        self.workers = workers or 1\n        self.proxy_headers = proxy_headers\n        self.server_header = server_header\n        self.date_header = date_header\n        self.root_path = root_path\n        self.limit_concurrency = limit_concurrency\n        self.limit_max_requests = limit_max_requests\n        self.limit_max_requests_jitter = limit_max_requests_jitter\n        self.backlog = backlog\n        self.timeout_keep_alive = timeout_keep_alive\n        self.timeout_notify = timeout_notify\n        self.timeout_graceful_shutdown = timeout_graceful_shutdown\n        self.timeout_worker_healthcheck = timeout_worker_healthcheck\n        self.callback_notify = callback_notify\n        self.ssl_keyfile = ssl_keyfile\n        self.ssl_certfile = ssl_certfile\n        self.ssl_keyfile_password = ssl_keyfile_password\n        self.ssl_version = ssl_version\n        self.ssl_cert_reqs = ssl_cert_reqs\n        self.ssl_ca_certs = ssl_ca_certs\n        self.ssl_ciphers = ssl_ciphers\n        self.headers: list[tuple[str, str]] = headers or []\n        self.encoded_headers: list[tuple[bytes, bytes]] = []\n        self.factory = factory\n        self.h11_max_incomplete_event_size = h11_max_incomplete_event_size\n\n        self.loaded = False\n        self.configure_logging()\n\n        self.reload_dirs: list[Path] = []\n        self.reload_dirs_excludes: list[Path] = []\n        self.reload_includes: list[str] = []\n        self.reload_excludes: list[str] = []\n\n        if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload:\n            logger.warning(\n                \"Current configuration will not reload as not all conditions are met, please refer to documentation.\"\n            )\n\n        if self.should_reload:\n            reload_dirs = _normalize_dirs(reload_dirs)\n            reload_includes = _normalize_dirs(reload_includes)\n            reload_excludes = _normalize_dirs(reload_excludes)\n\n            self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs)\n\n            self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, [])\n\n            reload_dirs_tmp = self.reload_dirs.copy()\n\n            for directory in self.reload_dirs_excludes:\n                for reload_directory in reload_dirs_tmp:\n                    if directory == reload_directory or directory in reload_directory.parents:\n                        try:\n                            self.reload_dirs.remove(reload_directory)\n                        except ValueError:  # pragma: full coverage\n                            pass\n\n            for pattern in self.reload_excludes:\n                if pattern in self.reload_includes:\n                    self.reload_includes.remove(pattern)  # pragma: full coverage\n\n            if not self.reload_dirs:\n                if reload_dirs:\n                    logger.warning(\n                        \"Provided reload directories %s did not contain valid \"\n                        + \"directories, watching current working directory.\",\n                        reload_dirs,\n                    )\n                self.reload_dirs = [Path.cwd()]\n\n            logger.info(\n                \"Will watch for changes in these directories: %s\",\n                sorted(list(map(str, self.reload_dirs))),\n            )\n\n        if env_file is not None:\n            from dotenv import load_dotenv\n\n            logger.info(\"Loading environment from '%s'\", env_file)\n            load_dotenv(dotenv_path=env_file)\n\n        if workers is None and \"WEB_CONCURRENCY\" in os.environ:\n            self.workers = int(os.environ[\"WEB_CONCURRENCY\"])\n\n        self.forwarded_allow_ips: list[str] | str\n        if forwarded_allow_ips is None:\n            self.forwarded_allow_ips = os.environ.get(\"FORWARDED_ALLOW_IPS\", \"127.0.0.1\")\n        else:\n            self.forwarded_allow_ips = forwarded_allow_ips  # pragma: full coverage\n\n        if self.reload and self.workers > 1:\n            logger.warning('\"workers\" flag is ignored when reloading is enabled.')\n\n    @property\n    def asgi_version(self) -> Literal[\"2.0\", \"3.0\"]:\n        mapping: dict[str, Literal[\"2.0\", \"3.0\"]] = {\n            \"asgi2\": \"2.0\",\n            \"asgi3\": \"3.0\",\n            \"wsgi\": \"3.0\",\n        }\n        return mapping[self.interface]\n\n    @property\n    def is_ssl(self) -> bool:\n        return bool(self.ssl_keyfile or self.ssl_certfile)\n\n    @property\n    def use_subprocess(self) -> bool:\n        return bool(self.reload or self.workers > 1)\n\n    def configure_logging(self) -> None:\n        logging.addLevelName(TRACE_LOG_LEVEL, \"TRACE\")\n\n        if self.log_config is not None:\n            if isinstance(self.log_config, dict):\n                if self.use_colors in (True, False):\n                    self.log_config[\"formatters\"][\"default\"][\"use_colors\"] = self.use_colors\n                    self.log_config[\"formatters\"][\"access\"][\"use_colors\"] = self.use_colors\n                logging.config.dictConfig(self.log_config)\n            elif isinstance(self.log_config, str) and self.log_config.endswith(\".json\"):\n                with open(self.log_config) as file:\n                    loaded_config = json.load(file)\n                    logging.config.dictConfig(loaded_config)\n            elif isinstance(self.log_config, str) and self.log_config.endswith((\".yaml\", \".yml\")):\n                # Install the PyYAML package or the uvicorn[standard] optional\n                # dependencies to enable this functionality.\n                import yaml\n\n                with open(self.log_config) as file:\n                    loaded_config = yaml.safe_load(file)\n                    logging.config.dictConfig(loaded_config)\n            else:\n                # See the note about fileConfig() here:\n                # https://docs.python.org/3/library/logging.config.html#configuration-file-format\n                logging.config.fileConfig(self.log_config, disable_existing_loggers=False)\n\n        if self.log_level is not None:\n            if isinstance(self.log_level, str):\n                log_level = LOG_LEVELS[self.log_level]\n            else:\n                log_level = self.log_level\n            logging.getLogger(\"uvicorn.error\").setLevel(log_level)\n            logging.getLogger(\"uvicorn.access\").setLevel(log_level)\n            logging.getLogger(\"uvicorn.asgi\").setLevel(log_level)\n        if self.access_log is False:\n            logging.getLogger(\"uvicorn.access\").handlers = []\n            logging.getLogger(\"uvicorn.access\").propagate = False\n\n    def load(self) -> None:\n        assert not self.loaded\n\n        if self.is_ssl:\n            assert self.ssl_certfile\n            self.ssl: ssl.SSLContext | None = create_ssl_context(\n                keyfile=self.ssl_keyfile,\n                certfile=self.ssl_certfile,\n                password=self.ssl_keyfile_password,\n                ssl_version=self.ssl_version,\n                cert_reqs=self.ssl_cert_reqs,\n                ca_certs=self.ssl_ca_certs,\n                ciphers=self.ssl_ciphers,\n            )\n        else:\n            self.ssl = None\n\n        encoded_headers = [(key.lower().encode(\"latin1\"), value.encode(\"latin1\")) for key, value in self.headers]\n        self.encoded_headers = (\n            [(b\"server\", b\"uvicorn\")] + encoded_headers\n            if b\"server\" not in dict(encoded_headers) and self.server_header\n            else encoded_headers\n        )\n\n        if isinstance(self.http, str):\n            http_protocol_class = import_from_string(HTTP_PROTOCOLS.get(self.http, self.http))\n            self.http_protocol_class: type[asyncio.Protocol] = http_protocol_class\n        else:\n            self.http_protocol_class = self.http\n\n        if isinstance(self.ws, str):\n            ws_protocol_class = import_from_string(WS_PROTOCOLS.get(self.ws, self.ws))\n            self.ws_protocol_class: type[asyncio.Protocol] | None = ws_protocol_class\n        else:\n            self.ws_protocol_class = self.ws\n\n        self.lifespan_class = import_from_string(LIFESPAN[self.lifespan])\n\n        try:\n            self.loaded_app = import_from_string(self.app)\n        except ImportFromStringError as exc:\n            logger.error(\"Error loading ASGI app. %s\" % exc)\n            sys.exit(1)\n\n        try:\n            self.loaded_app = self.loaded_app()\n        except TypeError as exc:\n            if self.factory:\n                logger.error(\"Error loading ASGI app factory: %s\", exc)\n                sys.exit(1)\n        else:\n            if not self.factory:\n                logger.warning(\n                    \"ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly.\"\n                )\n\n        if self.interface == \"auto\":\n            if inspect.isclass(self.loaded_app):\n                use_asgi_3 = hasattr(self.loaded_app, \"__await__\")\n            elif inspect.isfunction(self.loaded_app):\n                use_asgi_3 = iscoroutinefunction(self.loaded_app)\n            else:\n                call = getattr(self.loaded_app, \"__call__\", None)\n                use_asgi_3 = iscoroutinefunction(call)\n            self.interface = \"asgi3\" if use_asgi_3 else \"asgi2\"\n\n        if self.interface == \"wsgi\":\n            self.loaded_app = WSGIMiddleware(self.loaded_app)\n            self.ws_protocol_class = None\n        elif self.interface == \"asgi2\":\n            self.loaded_app = ASGI2Middleware(self.loaded_app)\n\n        if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL:\n            self.loaded_app = MessageLoggerMiddleware(self.loaded_app)\n        if self.proxy_headers:\n            self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips)\n\n        self.loaded = True\n\n    def setup_event_loop(self) -> None:\n        raise AttributeError(\n            \"The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\\n\"\n            \"None of those methods are supposed to be used directly. If you are doing it, please let me know here: \"\n            \"https://github.com/Kludex/uvicorn/discussions/2706. Thank you, and sorry for the inconvenience.\"\n        )\n\n    def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] | None:\n        if self.loop in LOOP_FACTORIES:\n            loop_factory: Callable[..., Any] | None = import_from_string(LOOP_FACTORIES[self.loop])\n        else:\n            try:\n                return import_from_string(self.loop)\n            except ImportFromStringError as exc:\n                logger.error(\"Error loading custom loop setup function. %s\" % exc)\n                sys.exit(1)\n        if loop_factory is None:\n            return None\n        return loop_factory(use_subprocess=self.use_subprocess)\n\n    def bind_socket(self) -> socket.socket:\n        logger_args: list[str | int]\n        if self.uds:  # pragma: py-win32\n            path = self.uds\n            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n            try:\n                sock.bind(path)\n                uds_perms = 0o666\n                os.chmod(self.uds, uds_perms)\n            except OSError as exc:  # pragma: full coverage\n                logger.error(exc)\n                sys.exit(1)\n\n            message = \"Uvicorn running on unix socket %s (Press CTRL+C to quit)\"\n            sock_name_format = \"%s\"\n            color_message = \"Uvicorn running on \" + click.style(sock_name_format, bold=True) + \" (Press CTRL+C to quit)\"\n            logger_args = [self.uds]\n        elif self.fd:  # pragma: py-win32\n            sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)\n            message = \"Uvicorn running on socket %s (Press CTRL+C to quit)\"\n            fd_name_format = \"%s\"\n            color_message = \"Uvicorn running on \" + click.style(fd_name_format, bold=True) + \" (Press CTRL+C to quit)\"\n            logger_args = [sock.getsockname()]\n        else:\n            family = socket.AF_INET\n            addr_format = \"%s://%s:%d\"\n\n            if self.host and \":\" in self.host:  # pragma: full coverage\n                # It's an IPv6 address.\n                family = socket.AF_INET6\n                addr_format = \"%s://[%s]:%d\"\n\n            sock = socket.socket(family=family)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            try:\n                sock.bind((self.host, self.port))\n            except OSError as exc:  # pragma: full coverage\n                logger.error(exc)\n                sys.exit(1)\n\n            message = f\"Uvicorn running on {addr_format} (Press CTRL+C to quit)\"\n            color_message = \"Uvicorn running on \" + click.style(addr_format, bold=True) + \" (Press CTRL+C to quit)\"\n            protocol_name = \"https\" if self.is_ssl else \"http\"\n            logger_args = [protocol_name, self.host, sock.getsockname()[1]]\n        logger.info(message, *logger_args, extra={\"color_message\": color_message})\n        sock.set_inheritable(True)\n        return sock\n\n    @property\n    def should_reload(self) -> bool:\n        return isinstance(self.app, str) and self.reload\n"
  },
  {
    "path": "uvicorn/importer.py",
    "content": "import importlib\nfrom typing import Any\n\n\nclass ImportFromStringError(Exception):\n    pass\n\n\ndef import_from_string(import_str: Any) -> Any:\n    if not isinstance(import_str, str):\n        return import_str\n\n    module_str, _, attrs_str = import_str.partition(\":\")\n    if not module_str or not attrs_str:\n        message = 'Import string \"{import_str}\" must be in format \"<module>:<attribute>\".'\n        raise ImportFromStringError(message.format(import_str=import_str))\n\n    try:\n        module = importlib.import_module(module_str)\n    except ModuleNotFoundError as exc:\n        if exc.name != module_str:\n            raise exc from None\n        message = 'Could not import module \"{module_str}\".'\n        raise ImportFromStringError(message.format(module_str=module_str))\n\n    instance = module\n    try:\n        for attr_str in attrs_str.split(\".\"):\n            instance = getattr(instance, attr_str)\n    except AttributeError:\n        message = 'Attribute \"{attrs_str}\" not found in module \"{module_str}\".'\n        raise ImportFromStringError(message.format(attrs_str=attrs_str, module_str=module_str))\n\n    return instance\n"
  },
  {
    "path": "uvicorn/lifespan/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/lifespan/off.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom uvicorn import Config\n\n\nclass LifespanOff:\n    def __init__(self, config: Config) -> None:\n        self.should_exit = False\n        self.state: dict[str, Any] = {}\n\n    async def startup(self) -> None:\n        pass\n\n    async def shutdown(self) -> None:\n        pass\n"
  },
  {
    "path": "uvicorn/lifespan/on.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nfrom asyncio import Queue\nfrom typing import Any\n\nfrom uvicorn import Config\nfrom uvicorn._types import (\n    LifespanScope,\n    LifespanShutdownCompleteEvent,\n    LifespanShutdownEvent,\n    LifespanShutdownFailedEvent,\n    LifespanStartupCompleteEvent,\n    LifespanStartupEvent,\n    LifespanStartupFailedEvent,\n)\n\nLifespanReceiveMessage = LifespanStartupEvent | LifespanShutdownEvent\nLifespanSendMessage = (\n    LifespanStartupFailedEvent\n    | LifespanShutdownFailedEvent\n    | LifespanStartupCompleteEvent\n    | LifespanShutdownCompleteEvent\n)\n\n\nSTATE_TRANSITION_ERROR = \"Got invalid state transition on lifespan protocol.\"\n\n\nclass LifespanOn:\n    def __init__(self, config: Config) -> None:\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.startup_event = asyncio.Event()\n        self.shutdown_event = asyncio.Event()\n        self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue()\n        self.error_occurred = False\n        self.startup_failed = False\n        self.shutdown_failed = False\n        self.should_exit = False\n        self.state: dict[str, Any] = {}\n\n    async def startup(self) -> None:\n        self.logger.info(\"Waiting for application startup.\")\n\n        loop = asyncio.get_event_loop()\n        main_lifespan_task = loop.create_task(self.main())  # noqa: F841\n        # Keep a hard reference to prevent garbage collection\n        # See https://github.com/Kludex/uvicorn/pull/972\n        startup_event: LifespanStartupEvent = {\"type\": \"lifespan.startup\"}\n        await self.receive_queue.put(startup_event)\n        await self.startup_event.wait()\n\n        if self.startup_failed or (self.error_occurred and self.config.lifespan == \"on\"):\n            self.logger.error(\"Application startup failed. Exiting.\")\n            self.should_exit = True\n        else:\n            self.logger.info(\"Application startup complete.\")\n\n    async def shutdown(self) -> None:\n        if self.error_occurred:\n            return\n        self.logger.info(\"Waiting for application shutdown.\")\n        shutdown_event: LifespanShutdownEvent = {\"type\": \"lifespan.shutdown\"}\n        await self.receive_queue.put(shutdown_event)\n        await self.shutdown_event.wait()\n\n        if self.shutdown_failed or (self.error_occurred and self.config.lifespan == \"on\"):\n            self.logger.error(\"Application shutdown failed. Exiting.\")\n            self.should_exit = True\n        else:\n            self.logger.info(\"Application shutdown complete.\")\n\n    async def main(self) -> None:\n        try:\n            app = self.config.loaded_app\n            scope: LifespanScope = {\n                \"type\": \"lifespan\",\n                \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.0\"},\n                \"state\": self.state,\n            }\n            await app(scope, self.receive, self.send)\n        except BaseException as exc:\n            self.asgi = None\n            self.error_occurred = True\n            if self.startup_failed or self.shutdown_failed:\n                return\n            if self.config.lifespan == \"auto\":\n                msg = \"ASGI 'lifespan' protocol appears unsupported.\"\n                self.logger.info(msg)\n            else:\n                msg = \"Exception in 'lifespan' protocol\\n\"\n                self.logger.error(msg, exc_info=exc)\n        finally:\n            self.startup_event.set()\n            self.shutdown_event.set()\n\n    async def send(self, message: LifespanSendMessage) -> None:\n        assert message[\"type\"] in (\n            \"lifespan.startup.complete\",\n            \"lifespan.startup.failed\",\n            \"lifespan.shutdown.complete\",\n            \"lifespan.shutdown.failed\",\n        )\n\n        if message[\"type\"] == \"lifespan.startup.complete\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n\n        elif message[\"type\"] == \"lifespan.startup.failed\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n            self.startup_failed = True\n            if message.get(\"message\"):\n                self.logger.error(message[\"message\"])\n\n        elif message[\"type\"] == \"lifespan.shutdown.complete\":\n            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.shutdown_event.set()\n\n        elif message[\"type\"] == \"lifespan.shutdown.failed\":\n            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.shutdown_event.set()\n            self.shutdown_failed = True\n            if message.get(\"message\"):\n                self.logger.error(message[\"message\"])\n\n    async def receive(self) -> LifespanReceiveMessage:\n        return await self.receive_queue.get()\n"
  },
  {
    "path": "uvicorn/logging.py",
    "content": "from __future__ import annotations\n\nimport http\nimport logging\nimport sys\nfrom copy import copy\nfrom typing import Literal\n\nimport click\n\nTRACE_LOG_LEVEL = 5\n\n\nclass ColourizedFormatter(logging.Formatter):\n    \"\"\"\n    A custom log formatter class that:\n\n    * Outputs the LOG_LEVEL with an appropriate color.\n    * If a log call includes an `extra={\"color_message\": ...}` it will be used\n      for formatting the output, instead of the plain text message.\n    \"\"\"\n\n    level_name_colors = {\n        TRACE_LOG_LEVEL: lambda level_name: click.style(str(level_name), fg=\"blue\"),\n        logging.DEBUG: lambda level_name: click.style(str(level_name), fg=\"cyan\"),\n        logging.INFO: lambda level_name: click.style(str(level_name), fg=\"green\"),\n        logging.WARNING: lambda level_name: click.style(str(level_name), fg=\"yellow\"),\n        logging.ERROR: lambda level_name: click.style(str(level_name), fg=\"red\"),\n        logging.CRITICAL: lambda level_name: click.style(str(level_name), fg=\"bright_red\"),\n    }\n\n    def __init__(\n        self,\n        fmt: str | None = None,\n        datefmt: str | None = None,\n        style: Literal[\"%\", \"{\", \"$\"] = \"%\",\n        use_colors: bool | None = None,\n    ):\n        if use_colors in (True, False):\n            self.use_colors = use_colors\n        else:\n            self.use_colors = sys.stdout.isatty()\n        super().__init__(fmt=fmt, datefmt=datefmt, style=style)\n\n    def color_level_name(self, level_name: str, level_no: int) -> str:\n        def default(level_name: str) -> str:\n            return str(level_name)  # pragma: no cover\n\n        func = self.level_name_colors.get(level_no, default)\n        return func(level_name)\n\n    def should_use_colors(self) -> bool:\n        return True  # pragma: no cover\n\n    def formatMessage(self, record: logging.LogRecord) -> str:\n        recordcopy = copy(record)\n        levelname = recordcopy.levelname\n        separator = \" \" * (8 - len(recordcopy.levelname))\n        if self.use_colors:\n            levelname = self.color_level_name(levelname, recordcopy.levelno)\n            if \"color_message\" in recordcopy.__dict__:\n                recordcopy.msg = recordcopy.__dict__[\"color_message\"]\n                recordcopy.__dict__[\"message\"] = recordcopy.getMessage()\n        recordcopy.__dict__[\"levelprefix\"] = levelname + \":\" + separator\n        return super().formatMessage(recordcopy)\n\n\nclass DefaultFormatter(ColourizedFormatter):\n    def should_use_colors(self) -> bool:\n        return sys.stderr.isatty()  # pragma: no cover\n\n\nclass AccessFormatter(ColourizedFormatter):\n    status_code_colours = {\n        1: lambda code: click.style(str(code), fg=\"bright_white\"),\n        2: lambda code: click.style(str(code), fg=\"green\"),\n        3: lambda code: click.style(str(code), fg=\"yellow\"),\n        4: lambda code: click.style(str(code), fg=\"red\"),\n        5: lambda code: click.style(str(code), fg=\"bright_red\"),\n    }\n\n    def get_status_code(self, status_code: int) -> str:\n        try:\n            status_phrase = http.HTTPStatus(status_code).phrase\n        except ValueError:\n            status_phrase = \"\"\n        status_and_phrase = f\"{status_code} {status_phrase}\"\n        if self.use_colors:\n\n            def default(code: int) -> str:\n                return status_and_phrase  # pragma: no cover\n\n            func = self.status_code_colours.get(status_code // 100, default)\n            return func(status_and_phrase)\n        return status_and_phrase\n\n    def formatMessage(self, record: logging.LogRecord) -> str:\n        recordcopy = copy(record)\n        (\n            client_addr,\n            method,\n            full_path,\n            http_version,\n            status_code,\n        ) = recordcopy.args  # type: ignore[misc]\n        status_code = self.get_status_code(int(status_code))  # type: ignore[arg-type]\n        request_line = f\"{method} {full_path} HTTP/{http_version}\"\n        if self.use_colors:\n            request_line = click.style(request_line, bold=True)\n        recordcopy.__dict__.update(\n            {\n                \"client_addr\": client_addr,\n                \"request_line\": request_line,\n                \"status_code\": status_code,\n            }\n        )\n        return super().formatMessage(recordcopy)\n"
  },
  {
    "path": "uvicorn/loops/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/loops/asyncio.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable\n\n\ndef asyncio_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:\n    if sys.platform == \"win32\" and not use_subprocess:\n        return asyncio.ProactorEventLoop\n    return asyncio.SelectorEventLoop\n"
  },
  {
    "path": "uvicorn/loops/auto.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\n\ndef auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:\n    try:\n        import uvloop  # noqa\n    except ImportError:  # pragma: no cover\n        from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory\n\n        return loop_factory(use_subprocess=use_subprocess)\n    else:  # pragma: no cover\n        from uvicorn.loops.uvloop import uvloop_loop_factory\n\n        return uvloop_loop_factory(use_subprocess=use_subprocess)\n"
  },
  {
    "path": "uvicorn/loops/uvloop.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\nimport uvloop\n\n\ndef uvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:\n    return uvloop.new_event_loop\n"
  },
  {
    "path": "uvicorn/main.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport os\nimport platform\nimport ssl\nimport sys\nimport warnings\nfrom collections.abc import Callable\nfrom configparser import RawConfigParser\nfrom typing import IO, Any, get_args\n\nimport click\n\nimport uvicorn\nfrom uvicorn._types import ASGIApplication\nfrom uvicorn.config import (\n    INTERFACES,\n    LIFESPAN,\n    LOG_LEVELS,\n    LOGGING_CONFIG,\n    SSL_PROTOCOL_VERSION,\n    Config,\n    HTTPProtocolType,\n    InterfaceType,\n    LifespanType,\n    LoopFactoryType,\n    WSProtocolType,\n)\nfrom uvicorn.server import Server\nfrom uvicorn.supervisors import ChangeReload, Multiprocess\n\nLEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys()))\nLIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys()))\nINTERFACE_CHOICES = click.Choice(INTERFACES)\n\n\ndef _metavar_from_type(_type: Any) -> str:\n    return f\"[{'|'.join(key for key in get_args(_type) if key != 'none')}]\"\n\n\nSTARTUP_FAILURE = 3\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\ndef print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None:\n    if not value or ctx.resilient_parsing:\n        return\n    click.echo(\n        \"Running uvicorn {version} with {py_implementation} {py_version} on {system}\".format(  # noqa: UP032\n            version=uvicorn.__version__,\n            py_implementation=platform.python_implementation(),\n            py_version=platform.python_version(),\n            system=platform.system(),\n        )\n    )\n    ctx.exit()\n\n\n@click.command(context_settings={\"auto_envvar_prefix\": \"UVICORN\"})\n@click.argument(\"app\", envvar=\"UVICORN_APP\")\n@click.option(\n    \"--host\",\n    type=str,\n    default=\"127.0.0.1\",\n    help=\"Bind socket to this host.\",\n    show_default=True,\n)\n@click.option(\n    \"--port\",\n    type=int,\n    default=8000,\n    help=\"Bind socket to this port. If 0, an available port will be picked.\",\n    show_default=True,\n)\n@click.option(\"--uds\", type=str, default=None, help=\"Bind to a UNIX domain socket.\")\n@click.option(\"--fd\", type=int, default=None, help=\"Bind to socket from this file descriptor.\")\n@click.option(\"--reload\", is_flag=True, default=False, help=\"Enable auto-reload.\")\n@click.option(\n    \"--reload-dir\",\n    \"reload_dirs\",\n    multiple=True,\n    help=\"Set reload directories explicitly, instead of using the current working directory.\",\n    type=click.Path(exists=True),\n)\n@click.option(\n    \"--reload-include\",\n    \"reload_includes\",\n    multiple=True,\n    help=\"Set glob patterns to include while watching for files. Includes '*.py' \"\n    \"by default; these defaults can be overridden with `--reload-exclude`. \"\n    \"This option has no effect unless watchfiles is installed.\",\n)\n@click.option(\n    \"--reload-exclude\",\n    \"reload_excludes\",\n    multiple=True,\n    help=\"Set glob patterns to exclude while watching for files. Includes \"\n    \"'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden \"\n    \"with `--reload-include`. This option has no effect unless watchfiles is \"\n    \"installed.\",\n)\n@click.option(\n    \"--reload-delay\",\n    type=float,\n    default=0.25,\n    show_default=True,\n    help=\"Delay between previous and next check if application needs to be. Defaults to 0.25s.\",\n)\n@click.option(\n    \"--workers\",\n    default=None,\n    type=int,\n    help=\"Number of worker processes. Defaults to the $WEB_CONCURRENCY environment\"\n    \" variable if available, or 1. Not valid with --reload.\",\n)\n@click.option(\n    \"--loop\",\n    type=str,\n    metavar=_metavar_from_type(LoopFactoryType),\n    default=\"auto\",\n    help=\"Event loop factory implementation.\",\n    show_default=True,\n)\n@click.option(\n    \"--http\",\n    type=str,\n    metavar=_metavar_from_type(HTTPProtocolType),\n    default=\"auto\",\n    help=\"HTTP protocol implementation.\",\n    show_default=True,\n)\n@click.option(\n    \"--ws\",\n    type=str,\n    metavar=_metavar_from_type(WSProtocolType),\n    default=\"auto\",\n    help=\"WebSocket protocol implementation.\",\n    show_default=True,\n)\n@click.option(\n    \"--ws-max-size\",\n    type=int,\n    default=16777216,\n    help=\"WebSocket max size message in bytes\",\n    show_default=True,\n)\n@click.option(\n    \"--ws-max-queue\",\n    type=int,\n    default=32,\n    help=\"The maximum length of the WebSocket message queue.\",\n    show_default=True,\n)\n@click.option(\n    \"--ws-ping-interval\",\n    type=float,\n    default=20.0,\n    help=\"WebSocket ping interval in seconds.\",\n    show_default=True,\n)\n@click.option(\n    \"--ws-ping-timeout\",\n    type=float,\n    default=20.0,\n    help=\"WebSocket ping timeout in seconds.\",\n    show_default=True,\n)\n@click.option(\n    \"--ws-per-message-deflate\",\n    type=bool,\n    default=True,\n    help=\"WebSocket per-message-deflate compression\",\n    show_default=True,\n)\n@click.option(\n    \"--lifespan\",\n    type=LIFESPAN_CHOICES,\n    default=\"auto\",\n    help=\"Lifespan implementation.\",\n    show_default=True,\n)\n@click.option(\n    \"--interface\",\n    type=INTERFACE_CHOICES,\n    default=\"auto\",\n    help=\"Select ASGI3, ASGI2, or WSGI as the application interface.\",\n    show_default=True,\n)\n@click.option(\n    \"--env-file\",\n    type=click.Path(exists=True),\n    default=None,\n    help=\"Environment configuration file.\",\n    show_default=True,\n)\n@click.option(\n    \"--log-config\",\n    type=click.Path(exists=True),\n    default=None,\n    help=\"Logging configuration file. Supported formats: .ini, .json, .yaml.\",\n    show_default=True,\n)\n@click.option(\n    \"--log-level\",\n    type=LEVEL_CHOICES,\n    default=None,\n    help=\"Log level. [default: info]\",\n    show_default=True,\n)\n@click.option(\n    \"--access-log/--no-access-log\",\n    is_flag=True,\n    default=True,\n    help=\"Enable/Disable access log.\",\n)\n@click.option(\n    \"--use-colors/--no-use-colors\",\n    is_flag=True,\n    default=None,\n    help=\"Enable/Disable colorized logging.\",\n)\n@click.option(\n    \"--proxy-headers/--no-proxy-headers\",\n    is_flag=True,\n    default=True,\n    help=\"Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate url scheme and remote address info.\",\n)\n@click.option(\n    \"--server-header/--no-server-header\",\n    is_flag=True,\n    default=True,\n    help=\"Enable/Disable default Server header.\",\n)\n@click.option(\n    \"--date-header/--no-date-header\",\n    is_flag=True,\n    default=True,\n    help=\"Enable/Disable default Date header.\",\n)\n@click.option(\n    \"--forwarded-allow-ips\",\n    type=str,\n    default=None,\n    help=\"Comma separated list of IP Addresses, IP Networks, or literals \"\n    \"(e.g. UNIX Socket path) to trust with proxy headers. Defaults to the \"\n    \"$FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. \"\n    \"The literal '*' means trust everything.\",\n)\n@click.option(\n    \"--root-path\",\n    type=str,\n    default=\"\",\n    help=\"Set the ASGI 'root_path' for applications submounted below a given URL path.\",\n)\n@click.option(\n    \"--limit-concurrency\",\n    type=int,\n    default=None,\n    help=\"Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses.\",\n)\n@click.option(\n    \"--backlog\",\n    type=int,\n    default=2048,\n    help=\"Maximum number of connections to hold in backlog\",\n)\n@click.option(\n    \"--limit-max-requests\",\n    type=int,\n    default=None,\n    help=\"Maximum number of requests to service before terminating the process.\",\n)\n@click.option(\n    \"--limit-max-requests-jitter\",\n    type=int,\n    default=0,\n    help=\"Maximum jitter to add to limit_max_requests.\"\n    \" Staggers worker restarts to avoid all workers restarting simultaneously.\",\n    show_default=True,\n)\n@click.option(\n    \"--timeout-keep-alive\",\n    type=int,\n    default=5,\n    help=\"Close Keep-Alive connections if no new data is received within this timeout (in seconds).\",\n    show_default=True,\n)\n@click.option(\n    \"--timeout-graceful-shutdown\",\n    type=int,\n    default=None,\n    help=\"Maximum number of seconds to wait for graceful shutdown.\",\n)\n@click.option(\n    \"--timeout-worker-healthcheck\",\n    type=int,\n    default=5,\n    help=\"Maximum number of seconds to wait for a worker to respond to a healthcheck.\",\n    show_default=True,\n)\n@click.option(\"--ssl-keyfile\", type=str, default=None, help=\"SSL key file\", show_default=True)\n@click.option(\n    \"--ssl-certfile\",\n    type=str,\n    default=None,\n    help=\"SSL certificate file\",\n    show_default=True,\n)\n@click.option(\n    \"--ssl-keyfile-password\",\n    type=str,\n    default=None,\n    help=\"SSL keyfile password\",\n    show_default=True,\n)\n@click.option(\n    \"--ssl-version\",\n    type=int,\n    default=int(SSL_PROTOCOL_VERSION),\n    help=\"SSL version to use (see stdlib ssl module's)\",\n    show_default=True,\n)\n@click.option(\n    \"--ssl-cert-reqs\",\n    type=int,\n    default=int(ssl.CERT_NONE),\n    help=\"Whether client certificate is required (see stdlib ssl module's)\",\n    show_default=True,\n)\n@click.option(\n    \"--ssl-ca-certs\",\n    type=str,\n    default=None,\n    help=\"CA certificates file\",\n    show_default=True,\n)\n@click.option(\n    \"--ssl-ciphers\",\n    type=str,\n    default=\"TLSv1\",\n    help=\"Ciphers to use (see stdlib ssl module's)\",\n    show_default=True,\n)\n@click.option(\n    \"--header\",\n    \"headers\",\n    multiple=True,\n    help=\"Specify custom default HTTP response headers as a Name:Value pair\",\n)\n@click.option(\n    \"--version\",\n    is_flag=True,\n    callback=print_version,\n    expose_value=False,\n    is_eager=True,\n    help=\"Display the uvicorn version and exit.\",\n)\n@click.option(\n    \"--app-dir\",\n    default=\"\",\n    show_default=True,\n    help=\"Look for APP in the specified directory, by adding this to the PYTHONPATH.\"\n    \" Defaults to the current working directory.\",\n)\n@click.option(\n    \"--h11-max-incomplete-event-size\",\n    \"h11_max_incomplete_event_size\",\n    type=int,\n    default=None,\n    help=\"For h11, the maximum number of bytes to buffer of an incomplete event.\",\n)\n@click.option(\n    \"--factory\",\n    is_flag=True,\n    default=False,\n    help=\"Treat APP as an application factory, i.e. a () -> <ASGI app> callable.\",\n    show_default=True,\n)\ndef main(\n    app: str,\n    host: str,\n    port: int,\n    uds: str,\n    fd: int,\n    loop: LoopFactoryType | str,\n    http: HTTPProtocolType | str,\n    ws: WSProtocolType | str,\n    ws_max_size: int,\n    ws_max_queue: int,\n    ws_ping_interval: float,\n    ws_ping_timeout: float,\n    ws_per_message_deflate: bool,\n    lifespan: LifespanType,\n    interface: InterfaceType,\n    reload: bool,\n    reload_dirs: list[str],\n    reload_includes: list[str],\n    reload_excludes: list[str],\n    reload_delay: float,\n    workers: int,\n    env_file: str,\n    log_config: str,\n    log_level: str,\n    access_log: bool,\n    proxy_headers: bool,\n    server_header: bool,\n    date_header: bool,\n    forwarded_allow_ips: str,\n    root_path: str,\n    limit_concurrency: int,\n    backlog: int,\n    limit_max_requests: int,\n    limit_max_requests_jitter: int,\n    timeout_keep_alive: int,\n    timeout_graceful_shutdown: int | None,\n    timeout_worker_healthcheck: int,\n    ssl_keyfile: str,\n    ssl_certfile: str,\n    ssl_keyfile_password: str,\n    ssl_version: int,\n    ssl_cert_reqs: int,\n    ssl_ca_certs: str,\n    ssl_ciphers: str,\n    headers: list[str],\n    use_colors: bool,\n    app_dir: str,\n    h11_max_incomplete_event_size: int | None,\n    factory: bool,\n) -> None:\n    run(\n        app,\n        host=host,\n        port=port,\n        uds=uds,\n        fd=fd,\n        loop=loop,\n        http=http,\n        ws=ws,\n        ws_max_size=ws_max_size,\n        ws_max_queue=ws_max_queue,\n        ws_ping_interval=ws_ping_interval,\n        ws_ping_timeout=ws_ping_timeout,\n        ws_per_message_deflate=ws_per_message_deflate,\n        lifespan=lifespan,\n        env_file=env_file,\n        log_config=LOGGING_CONFIG if log_config is None else log_config,\n        log_level=log_level,\n        access_log=access_log,\n        interface=interface,\n        reload=reload,\n        reload_dirs=reload_dirs or None,\n        reload_includes=reload_includes or None,\n        reload_excludes=reload_excludes or None,\n        reload_delay=reload_delay,\n        workers=workers,\n        proxy_headers=proxy_headers,\n        server_header=server_header,\n        date_header=date_header,\n        forwarded_allow_ips=forwarded_allow_ips,\n        root_path=root_path,\n        limit_concurrency=limit_concurrency,\n        backlog=backlog,\n        limit_max_requests=limit_max_requests,\n        limit_max_requests_jitter=limit_max_requests_jitter,\n        timeout_keep_alive=timeout_keep_alive,\n        timeout_graceful_shutdown=timeout_graceful_shutdown,\n        timeout_worker_healthcheck=timeout_worker_healthcheck,\n        ssl_keyfile=ssl_keyfile,\n        ssl_certfile=ssl_certfile,\n        ssl_keyfile_password=ssl_keyfile_password,\n        ssl_version=ssl_version,\n        ssl_cert_reqs=ssl_cert_reqs,\n        ssl_ca_certs=ssl_ca_certs,\n        ssl_ciphers=ssl_ciphers,\n        headers=[header.split(\":\", 1) for header in headers],  # type: ignore[misc]\n        use_colors=use_colors,\n        factory=factory,\n        app_dir=app_dir,\n        h11_max_incomplete_event_size=h11_max_incomplete_event_size,\n    )\n\n\ndef run(\n    app: ASGIApplication | Callable[..., Any] | str,\n    *,\n    host: str = \"127.0.0.1\",\n    port: int = 8000,\n    uds: str | None = None,\n    fd: int | None = None,\n    loop: LoopFactoryType | str = \"auto\",\n    http: type[asyncio.Protocol] | HTTPProtocolType | str = \"auto\",\n    ws: type[asyncio.Protocol] | WSProtocolType | str = \"auto\",\n    ws_max_size: int = 16777216,\n    ws_max_queue: int = 32,\n    ws_ping_interval: float | None = 20.0,\n    ws_ping_timeout: float | None = 20.0,\n    ws_per_message_deflate: bool = True,\n    lifespan: LifespanType = \"auto\",\n    interface: InterfaceType = \"auto\",\n    reload: bool = False,\n    reload_dirs: list[str] | str | None = None,\n    reload_includes: list[str] | str | None = None,\n    reload_excludes: list[str] | str | None = None,\n    reload_delay: float = 0.25,\n    workers: int | None = None,\n    env_file: str | os.PathLike[str] | None = None,\n    log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = LOGGING_CONFIG,\n    log_level: str | int | None = None,\n    access_log: bool = True,\n    proxy_headers: bool = True,\n    server_header: bool = True,\n    date_header: bool = True,\n    forwarded_allow_ips: list[str] | str | None = None,\n    root_path: str = \"\",\n    limit_concurrency: int | None = None,\n    backlog: int = 2048,\n    limit_max_requests: int | None = None,\n    limit_max_requests_jitter: int = 0,\n    timeout_keep_alive: int = 5,\n    timeout_graceful_shutdown: int | None = None,\n    timeout_worker_healthcheck: int = 5,\n    ssl_keyfile: str | os.PathLike[str] | None = None,\n    ssl_certfile: str | os.PathLike[str] | None = None,\n    ssl_keyfile_password: str | None = None,\n    ssl_version: int = SSL_PROTOCOL_VERSION,\n    ssl_cert_reqs: int = ssl.CERT_NONE,\n    ssl_ca_certs: str | os.PathLike[str] | None = None,\n    ssl_ciphers: str = \"TLSv1\",\n    headers: list[tuple[str, str]] | None = None,\n    use_colors: bool | None = None,\n    app_dir: str | None = None,\n    factory: bool = False,\n    h11_max_incomplete_event_size: int | None = None,\n) -> None:\n    if app_dir is not None:\n        sys.path.insert(0, app_dir)\n\n    config = Config(\n        app,\n        host=host,\n        port=port,\n        uds=uds,\n        fd=fd,\n        loop=loop,\n        http=http,\n        ws=ws,\n        ws_max_size=ws_max_size,\n        ws_max_queue=ws_max_queue,\n        ws_ping_interval=ws_ping_interval,\n        ws_ping_timeout=ws_ping_timeout,\n        ws_per_message_deflate=ws_per_message_deflate,\n        lifespan=lifespan,\n        interface=interface,\n        reload=reload,\n        reload_dirs=reload_dirs,\n        reload_includes=reload_includes,\n        reload_excludes=reload_excludes,\n        reload_delay=reload_delay,\n        workers=workers,\n        env_file=env_file,\n        log_config=log_config,\n        log_level=log_level,\n        access_log=access_log,\n        proxy_headers=proxy_headers,\n        server_header=server_header,\n        date_header=date_header,\n        forwarded_allow_ips=forwarded_allow_ips,\n        root_path=root_path,\n        limit_concurrency=limit_concurrency,\n        backlog=backlog,\n        limit_max_requests=limit_max_requests,\n        limit_max_requests_jitter=limit_max_requests_jitter,\n        timeout_keep_alive=timeout_keep_alive,\n        timeout_graceful_shutdown=timeout_graceful_shutdown,\n        timeout_worker_healthcheck=timeout_worker_healthcheck,\n        ssl_keyfile=ssl_keyfile,\n        ssl_certfile=ssl_certfile,\n        ssl_keyfile_password=ssl_keyfile_password,\n        ssl_version=ssl_version,\n        ssl_cert_reqs=ssl_cert_reqs,\n        ssl_ca_certs=ssl_ca_certs,\n        ssl_ciphers=ssl_ciphers,\n        headers=headers,\n        use_colors=use_colors,\n        factory=factory,\n        h11_max_incomplete_event_size=h11_max_incomplete_event_size,\n    )\n    server = Server(config=config)\n\n    if (config.reload or config.workers > 1) and not isinstance(app, str):\n        logger = logging.getLogger(\"uvicorn.error\")\n        logger.warning(\"You must pass the application as an import string to enable 'reload' or 'workers'.\")\n        sys.exit(1)\n\n    try:\n        if config.should_reload:\n            sock = config.bind_socket()\n            ChangeReload(config, target=server.run, sockets=[sock]).run()\n        elif config.workers > 1:\n            sock = config.bind_socket()\n            Multiprocess(config, target=server.run, sockets=[sock]).run()\n        else:\n            server.run()\n    except KeyboardInterrupt:\n        pass  # pragma: full coverage\n    finally:\n        if config.uds and os.path.exists(config.uds):\n            os.remove(config.uds)  # pragma: py-win32\n\n    if not server.started and not config.should_reload and config.workers == 1:\n        sys.exit(STARTUP_FAILURE)\n\n\ndef __getattr__(name: str) -> Any:\n    if name == \"ServerState\":\n        warnings.warn(\n            \"uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.\",\n            DeprecationWarning,\n        )\n        from uvicorn.server import ServerState\n\n        return ServerState\n    raise AttributeError(f\"module {__name__} has no attribute {name}\")\n\n\nif __name__ == \"__main__\":\n    main()  # pragma: no cover\n"
  },
  {
    "path": "uvicorn/middleware/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/middleware/asgi2.py",
    "content": "from uvicorn._types import (\n    ASGI2Application,\n    ASGIReceiveCallable,\n    ASGISendCallable,\n    Scope,\n)\n\n\nclass ASGI2Middleware:\n    def __init__(self, app: \"ASGI2Application\"):\n        self.app = app\n\n    async def __call__(self, scope: \"Scope\", receive: \"ASGIReceiveCallable\", send: \"ASGISendCallable\") -> None:\n        instance = self.app(scope)\n        await instance(receive, send)\n"
  },
  {
    "path": "uvicorn/middleware/message_logger.py",
    "content": "import logging\nfrom typing import Any\n\nfrom uvicorn._types import (\n    ASGI3Application,\n    ASGIReceiveCallable,\n    ASGIReceiveEvent,\n    ASGISendCallable,\n    ASGISendEvent,\n    WWWScope,\n)\nfrom uvicorn.logging import TRACE_LOG_LEVEL\n\nPLACEHOLDER_FORMAT = {\n    \"body\": \"<{length} bytes>\",\n    \"bytes\": \"<{length} bytes>\",\n    \"text\": \"<{length} chars>\",\n    \"headers\": \"<...>\",\n}\n\n\ndef message_with_placeholders(message: Any) -> Any:\n    \"\"\"\n    Return an ASGI message, with any body-type content omitted and replaced\n    with a placeholder.\n    \"\"\"\n    new_message = message.copy()\n    for attr in PLACEHOLDER_FORMAT.keys():\n        if message.get(attr) is not None:\n            content = message[attr]\n            placeholder = PLACEHOLDER_FORMAT[attr].format(length=len(content))\n            new_message[attr] = placeholder\n    return new_message\n\n\nclass MessageLoggerMiddleware:\n    def __init__(self, app: \"ASGI3Application\"):\n        self.task_counter = 0\n        self.app = app\n        self.logger = logging.getLogger(\"uvicorn.asgi\")\n\n        def trace(message: Any, *args: Any, **kwargs: Any) -> None:\n            self.logger.log(TRACE_LOG_LEVEL, message, *args, **kwargs)\n\n        self.logger.trace = trace  # type: ignore\n\n    async def __call__(\n        self,\n        scope: \"WWWScope\",\n        receive: \"ASGIReceiveCallable\",\n        send: \"ASGISendCallable\",\n    ) -> None:\n        self.task_counter += 1\n\n        task_counter = self.task_counter\n        client = scope.get(\"client\")\n        prefix = \"%s:%d - ASGI\" % (client[0], client[1]) if client else \"ASGI\"\n\n        async def inner_receive() -> \"ASGIReceiveEvent\":\n            message = await receive()\n            logged_message = message_with_placeholders(message)\n            log_text = \"%s [%d] Receive %s\"\n            self.logger.trace(  # type: ignore\n                log_text, prefix, task_counter, logged_message\n            )\n            return message\n\n        async def inner_send(message: \"ASGISendEvent\") -> None:\n            logged_message = message_with_placeholders(message)\n            log_text = \"%s [%d] Send %s\"\n            self.logger.trace(  # type: ignore\n                log_text, prefix, task_counter, logged_message\n            )\n            await send(message)\n\n        logged_scope = message_with_placeholders(scope)\n        log_text = \"%s [%d] Started scope=%s\"\n        self.logger.trace(log_text, prefix, task_counter, logged_scope)  # type: ignore\n        try:\n            await self.app(scope, inner_receive, inner_send)\n        except BaseException as exc:\n            log_text = \"%s [%d] Raised exception\"\n            self.logger.trace(log_text, prefix, task_counter)  # type: ignore\n            raise exc from None\n        else:\n            log_text = \"%s [%d] Completed\"\n            self.logger.trace(log_text, prefix, task_counter)  # type: ignore\n"
  },
  {
    "path": "uvicorn/middleware/proxy_headers.py",
    "content": "from __future__ import annotations\n\nimport ipaddress\n\nfrom uvicorn._types import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope\n\n\nclass ProxyHeadersMiddleware:\n    \"\"\"Middleware for handling known proxy headers\n\n    This middleware can be used when a known proxy is fronting the application,\n    and is trusted to be properly setting the `X-Forwarded-Proto` and\n    `X-Forwarded-For` headers with the connecting client information.\n\n    Modifies the `client` and `scheme` information so that they reference\n    the connecting client, rather that the connecting proxy.\n\n    References:\n    - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Proxies>\n    - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For>\n    \"\"\"\n\n    def __init__(self, app: ASGI3Application, trusted_hosts: list[str] | str = \"127.0.0.1\") -> None:\n        self.app = app\n        self.trusted_hosts = _TrustedHosts(trusted_hosts)\n\n    async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        if scope[\"type\"] == \"lifespan\":\n            return await self.app(scope, receive, send)\n\n        client_addr = scope.get(\"client\")\n        client_host = client_addr[0] if client_addr else None\n\n        if client_host in self.trusted_hosts:\n            headers = dict(scope[\"headers\"])\n\n            if b\"x-forwarded-proto\" in headers:\n                x_forwarded_proto = headers[b\"x-forwarded-proto\"].decode(\"latin1\").strip()\n\n                if x_forwarded_proto in {\"http\", \"https\", \"ws\", \"wss\"}:\n                    if scope[\"type\"] == \"websocket\":\n                        scope[\"scheme\"] = x_forwarded_proto.replace(\"http\", \"ws\")\n                    else:\n                        scope[\"scheme\"] = x_forwarded_proto\n\n            if b\"x-forwarded-for\" in headers:\n                x_forwarded_for = headers[b\"x-forwarded-for\"].decode(\"latin1\")\n                host = self.trusted_hosts.get_trusted_client_host(x_forwarded_for)\n\n                if host:\n                    # If the x-forwarded-for header is empty then host is an empty string.\n                    # Only set the client if we actually got something usable.\n                    # See: https://github.com/Kludex/uvicorn/issues/1068\n\n                    # We've lost the connecting client's port information by now,\n                    # so only include the host.\n                    port = 0\n                    scope[\"client\"] = (host, port)\n\n        return await self.app(scope, receive, send)\n\n\ndef _parse_raw_hosts(value: str) -> list[str]:\n    return [item.strip() for item in value.split(\",\")]\n\n\nclass _TrustedHosts:\n    \"\"\"Container for trusted hosts and networks\"\"\"\n\n    def __init__(self, trusted_hosts: list[str] | str) -> None:\n        self.always_trust: bool = trusted_hosts in (\"*\", [\"*\"])\n\n        self.trusted_literals: set[str] = set()\n        self.trusted_hosts: set[ipaddress.IPv4Address | ipaddress.IPv6Address] = set()\n        self.trusted_networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set()\n\n        # Notes:\n        # - We separate hosts from literals as there are many ways to write\n        #   an IPv6 Address so we need to compare by object.\n        # - We don't convert IP Address to single host networks (e.g. /32 / 128) as\n        #   it more efficient to do an address lookup in a set than check for\n        #   membership in each network.\n        # - We still allow literals as it might be possible that we receive a\n        #   something that isn't an IP Address e.g. a unix socket.\n\n        if not self.always_trust:\n            if isinstance(trusted_hosts, str):\n                trusted_hosts = _parse_raw_hosts(trusted_hosts)\n\n            for host in trusted_hosts:\n                # Note: because we always convert invalid IP types to literals it\n                # is not possible for the user to know they provided a malformed IP\n                # type - this may lead to unexpected / difficult to debug behaviour.\n\n                if \"/\" in host:\n                    # Looks like a network\n                    try:\n                        self.trusted_networks.add(ipaddress.ip_network(host))\n                    except ValueError:\n                        # Was not a valid IP Network\n                        self.trusted_literals.add(host)\n                else:\n                    try:\n                        self.trusted_hosts.add(ipaddress.ip_address(host))\n                    except ValueError:\n                        # Was not a valid IP Address\n                        self.trusted_literals.add(host)\n\n    def __contains__(self, host: str | None) -> bool:\n        if self.always_trust:\n            return True\n\n        if not host:\n            return False\n\n        try:\n            ip = ipaddress.ip_address(host)\n            if ip in self.trusted_hosts:\n                return True\n            return any(ip in net for net in self.trusted_networks)\n\n        except ValueError:\n            return host in self.trusted_literals\n\n    def get_trusted_client_host(self, x_forwarded_for: str) -> str:\n        \"\"\"Extract the client host from x_forwarded_for header\n\n        In general this is the first \"untrusted\" host in the forwarded for list.\n        \"\"\"\n        x_forwarded_for_hosts = _parse_raw_hosts(x_forwarded_for)\n\n        if self.always_trust:\n            return x_forwarded_for_hosts[0]\n\n        # Note: each proxy appends to the header list so check it in reverse order\n        for host in reversed(x_forwarded_for_hosts):\n            if host not in self:\n                return host\n\n        # All hosts are trusted meaning that the client was also a trusted proxy\n        # See https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576\n        return x_forwarded_for_hosts[0]\n"
  },
  {
    "path": "uvicorn/middleware/wsgi.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport concurrent.futures\nimport io\nimport sys\nimport warnings\nfrom collections import deque\nfrom collections.abc import Iterable\n\nfrom uvicorn._types import (\n    ASGIReceiveCallable,\n    ASGIReceiveEvent,\n    ASGISendCallable,\n    ASGISendEvent,\n    Environ,\n    ExcInfo,\n    HTTPRequestEvent,\n    HTTPResponseBodyEvent,\n    HTTPResponseStartEvent,\n    HTTPScope,\n    StartResponse,\n    WSGIApp,\n)\n\n\ndef build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO) -> Environ:\n    \"\"\"\n    Builds a scope and request message into a WSGI environ object.\n    \"\"\"\n    script_name = scope.get(\"root_path\", \"\").encode(\"utf8\").decode(\"latin1\")\n    path_info = scope[\"path\"].encode(\"utf8\").decode(\"latin1\")\n    if path_info.startswith(script_name):\n        path_info = path_info[len(script_name) :]\n    environ = {\n        \"REQUEST_METHOD\": scope[\"method\"],\n        \"SCRIPT_NAME\": script_name,\n        \"PATH_INFO\": path_info,\n        \"QUERY_STRING\": scope[\"query_string\"].decode(\"ascii\"),\n        \"SERVER_PROTOCOL\": \"HTTP/%s\" % scope[\"http_version\"],\n        \"wsgi.version\": (1, 0),\n        \"wsgi.url_scheme\": scope.get(\"scheme\", \"http\"),\n        \"wsgi.input\": body,\n        \"wsgi.errors\": sys.stdout,\n        \"wsgi.multithread\": True,\n        \"wsgi.multiprocess\": True,\n        \"wsgi.run_once\": False,\n    }\n\n    # Get server name and port - required in WSGI, not in ASGI\n    server = scope.get(\"server\")\n    if server is None:\n        server = (\"localhost\", 80)\n    environ[\"SERVER_NAME\"] = server[0]\n    environ[\"SERVER_PORT\"] = server[1]\n\n    # Get client IP address\n    client = scope.get(\"client\")\n    if client is not None:\n        environ[\"REMOTE_ADDR\"] = client[0]\n\n    # Go through headers and make them into environ entries\n    for name, value in scope.get(\"headers\", []):\n        name_str: str = name.decode(\"latin1\")\n        if name_str == \"content-length\":\n            corrected_name = \"CONTENT_LENGTH\"\n        elif name_str == \"content-type\":\n            corrected_name = \"CONTENT_TYPE\"\n        else:\n            corrected_name = \"HTTP_%s\" % name_str.upper().replace(\"-\", \"_\")\n        # HTTPbis say only ASCII chars are allowed in headers, but we latin1\n        # just in case\n        value_str: str = value.decode(\"latin1\")\n        if corrected_name in environ:\n            corrected_name_environ = environ[corrected_name]\n            assert isinstance(corrected_name_environ, str)\n            value_str = corrected_name_environ + \",\" + value_str\n        environ[corrected_name] = value_str\n    return environ\n\n\nclass _WSGIMiddleware:\n    def __init__(self, app: WSGIApp, workers: int = 10):\n        warnings.warn(\n            \"Uvicorn's native WSGI implementation is deprecated, you should switch to a2wsgi (`pip install a2wsgi`).\",\n            DeprecationWarning,\n        )\n        self.app = app\n        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)\n\n    async def __call__(\n        self,\n        scope: HTTPScope,\n        receive: ASGIReceiveCallable,\n        send: ASGISendCallable,\n    ) -> None:\n        assert scope[\"type\"] == \"http\"\n        instance = WSGIResponder(self.app, self.executor, scope)\n        await instance(receive, send)\n\n\nclass WSGIResponder:\n    def __init__(\n        self,\n        app: WSGIApp,\n        executor: concurrent.futures.ThreadPoolExecutor,\n        scope: HTTPScope,\n    ):\n        self.app = app\n        self.executor = executor\n        self.scope = scope\n        self.status = None\n        self.response_headers = None\n        self.send_event = asyncio.Event()\n        self.send_queue: deque[ASGISendEvent | None] = deque()\n        self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()\n        self.response_started = False\n        self.exc_info: ExcInfo | None = None\n\n    async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n        body = io.BytesIO(message.get(\"body\", b\"\"))\n        more_body = message.get(\"more_body\", False)\n        if more_body:\n            body.seek(0, io.SEEK_END)\n            while more_body:\n                body_message: HTTPRequestEvent = (\n                    await receive()  # type: ignore[assignment]\n                )\n                body.write(body_message.get(\"body\", b\"\"))\n                more_body = body_message.get(\"more_body\", False)\n            body.seek(0)\n        environ = build_environ(self.scope, message, body)\n        self.loop = asyncio.get_event_loop()\n        wsgi = self.loop.run_in_executor(self.executor, self.wsgi, environ, self.start_response)\n        sender = self.loop.create_task(self.sender(send))\n        try:\n            await asyncio.wait_for(wsgi, None)\n        finally:\n            self.send_queue.append(None)\n            self.send_event.set()\n            await asyncio.wait_for(sender, None)\n        if self.exc_info is not None:\n            raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])\n\n    async def sender(self, send: ASGISendCallable) -> None:\n        while True:\n            if self.send_queue:\n                message = self.send_queue.popleft()\n                if message is None:\n                    return\n                await send(message)\n            else:  # pragma: no cover\n                await self.send_event.wait()\n                self.send_event.clear()\n\n    def start_response(\n        self,\n        status: str,\n        response_headers: Iterable[tuple[str, str]],\n        exc_info: ExcInfo | None = None,\n    ) -> None:\n        self.exc_info = exc_info\n        if not self.response_started:\n            self.response_started = True\n            status_code_str, _ = status.split(\" \", 1)\n            status_code = int(status_code_str)\n            headers = [(name.encode(\"ascii\"), value.encode(\"ascii\")) for name, value in response_headers]\n            http_response_start_event: HTTPResponseStartEvent = {\n                \"type\": \"http.response.start\",\n                \"status\": status_code,\n                \"headers\": headers,\n            }\n            self.send_queue.append(http_response_start_event)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n    def wsgi(self, environ: Environ, start_response: StartResponse) -> None:\n        for chunk in self.app(environ, start_response):  # type: ignore\n            response_body: HTTPResponseBodyEvent = {\n                \"type\": \"http.response.body\",\n                \"body\": chunk,\n                \"more_body\": True,\n            }\n            self.send_queue.append(response_body)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n        empty_body: HTTPResponseBodyEvent = {\n            \"type\": \"http.response.body\",\n            \"body\": b\"\",\n            \"more_body\": False,\n        }\n        self.send_queue.append(empty_body)\n        self.loop.call_soon_threadsafe(self.send_event.set)\n\n\ntry:\n    from a2wsgi import WSGIMiddleware\nexcept ModuleNotFoundError:  # pragma: no cover\n    WSGIMiddleware = _WSGIMiddleware  # type: ignore[misc, assignment]\n"
  },
  {
    "path": "uvicorn/protocols/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/protocols/http/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/protocols/http/auto.py",
    "content": "from __future__ import annotations\n\nimport asyncio\n\nAutoHTTPProtocol: type[asyncio.Protocol]\ntry:\n    import httptools  # noqa\nexcept ImportError:  # pragma: no cover\n    from uvicorn.protocols.http.h11_impl import H11Protocol\n\n    AutoHTTPProtocol = H11Protocol\nelse:  # pragma: no cover\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n\n    AutoHTTPProtocol = HttpToolsProtocol\n"
  },
  {
    "path": "uvicorn/protocols/http/flow_control.py",
    "content": "import asyncio\n\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\nCLOSE_HEADER = (b\"connection\", b\"close\")\n\nHIGH_WATER_LIMIT = 65536\n\n\nclass FlowControl:\n    def __init__(self, transport: asyncio.Transport) -> None:\n        self._transport = transport\n        self.read_paused = False\n        self.write_paused = False\n        self._is_writable_event = asyncio.Event()\n        self._is_writable_event.set()\n\n    async def drain(self) -> None:\n        await self._is_writable_event.wait()  # pragma: full coverage\n\n    def pause_reading(self) -> None:\n        if not self.read_paused:\n            self.read_paused = True\n            self._transport.pause_reading()\n\n    def resume_reading(self) -> None:\n        if self.read_paused:\n            self.read_paused = False\n            self._transport.resume_reading()\n\n    def pause_writing(self) -> None:\n        if not self.write_paused:  # pragma: full coverage\n            self.write_paused = True\n            self._is_writable_event.clear()\n\n    def resume_writing(self) -> None:\n        if self.write_paused:  # pragma: full coverage\n            self.write_paused = False\n            self._is_writable_event.set()\n\n\nasync def service_unavailable(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 503,\n            \"headers\": [\n                (b\"content-type\", b\"text/plain; charset=utf-8\"),\n                (b\"content-length\", b\"19\"),\n                (b\"connection\", b\"close\"),\n            ],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": b\"Service Unavailable\", \"more_body\": False})\n"
  },
  {
    "path": "uvicorn/protocols/http/h11_impl.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport contextvars\nimport http\nimport logging\nfrom collections.abc import Callable\nfrom typing import Any, Literal, cast\nfrom urllib.parse import unquote\n\nimport h11\nfrom h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE\n\nfrom uvicorn._types import (\n    ASGI3Application,\n    ASGIReceiveEvent,\n    ASGISendEvent,\n    HTTPRequestEvent,\n    HTTPResponseBodyEvent,\n    HTTPResponseStartEvent,\n    HTTPScope,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable\nfrom uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl\nfrom uvicorn.server import ServerState\n\n\ndef _get_status_phrase(status_code: int) -> bytes:\n    try:\n        return http.HTTPStatus(status_code).phrase.encode()\n    except ValueError:\n        return b\"\"\n\n\nSTATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)}\n\n\nclass H11Protocol(asyncio.Protocol):\n    def __init__(\n        self,\n        config: Config,\n        server_state: ServerState,\n        app_state: dict[str, Any],\n        _loop: asyncio.AbstractEventLoop | None = None,\n    ) -> None:\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.app = config.loaded_app\n        self.loop = _loop or asyncio.get_event_loop()\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.access_logger = logging.getLogger(\"uvicorn.access\")\n        self.access_log = self.access_logger.hasHandlers()\n        self.conn = h11.Connection(\n            h11.SERVER,\n            config.h11_max_incomplete_event_size\n            if config.h11_max_incomplete_event_size is not None\n            else DEFAULT_MAX_INCOMPLETE_EVENT_SIZE,\n        )\n        self.ws_protocol_class = config.ws_protocol_class\n        self.root_path = config.root_path\n        self.limit_concurrency = config.limit_concurrency\n        self.app_state = app_state\n\n        # Timeouts\n        self.timeout_keep_alive_task: asyncio.TimerHandle | None = None\n        self.timeout_keep_alive = config.timeout_keep_alive\n\n        # Shared server state\n        self.server_state = server_state\n        self.connections = server_state.connections\n        self.tasks = server_state.tasks\n\n        # Per-connection state\n        self.transport: asyncio.Transport = None  # type: ignore[assignment]\n        self.flow: FlowControl = None  # type: ignore[assignment]\n        self.server: tuple[str, int | None] | None = None\n        self.client: tuple[str, int] | None = None\n        self.scheme: Literal[\"http\", \"https\"] | None = None\n\n        # Per-request state\n        self.scope: HTTPScope = None  # type: ignore[assignment]\n        self.headers: list[tuple[bytes, bytes]] = None  # type: ignore[assignment]\n        self.cycle: RequestResponseCycle = None  # type: ignore[assignment]\n\n    # Protocol interface\n    def connection_made(  # type: ignore[override]\n        self, transport: asyncio.Transport\n    ) -> None:\n        self.connections.add(self)\n\n        self.transport = transport\n        self.flow = FlowControl(transport)\n        self.server = get_local_addr(transport)\n        self.client = get_remote_addr(transport)\n        self.scheme = \"https\" if is_ssl(transport) else \"http\"\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sHTTP connection made\", prefix)\n\n    def connection_lost(self, exc: Exception | None) -> None:\n        self.connections.discard(self)\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sHTTP connection lost\", prefix)\n\n        if self.cycle and not self.cycle.response_complete:\n            self.cycle.disconnected = True\n        if self.conn.our_state != h11.ERROR:\n            event = h11.ConnectionClosed()\n            try:\n                self.conn.send(event)\n            except h11.LocalProtocolError:\n                # Premature client disconnect\n                pass\n\n        if self.cycle is not None:\n            self.cycle.message_event.set()\n        if self.flow is not None:\n            self.flow.resume_writing()\n        if exc is None:\n            self.transport.close()\n            self._unset_keepalive_if_required()\n\n    def eof_received(self) -> None:\n        pass\n\n    def _unset_keepalive_if_required(self) -> None:\n        if self.timeout_keep_alive_task is not None:\n            self.timeout_keep_alive_task.cancel()\n            self.timeout_keep_alive_task = None\n\n    def _get_upgrade(self) -> bytes | None:\n        connection = []\n        upgrade = None\n        for name, value in self.headers:\n            if name == b\"connection\":\n                connection = [token.lower().strip() for token in value.split(b\",\")]\n            if name == b\"upgrade\":\n                upgrade = value.lower()\n        if b\"upgrade\" in connection:\n            return upgrade\n        return None\n\n    def _should_upgrade_to_ws(self) -> bool:\n        if self.ws_protocol_class is None:\n            return False\n        return True\n\n    def _unsupported_upgrade_warning(self) -> None:\n        msg = \"Unsupported upgrade request.\"\n        self.logger.warning(msg)\n        if not self._should_upgrade_to_ws():\n            msg = \"No supported WebSocket library detected. Please use \\\"pip install 'uvicorn[standard]'\\\", or install 'websockets' or 'wsproto' manually.\"  # noqa: E501\n            self.logger.warning(msg)\n\n    def _should_upgrade(self) -> bool:\n        upgrade = self._get_upgrade()\n        if upgrade == b\"websocket\" and self._should_upgrade_to_ws():\n            return True\n        if upgrade is not None:\n            self._unsupported_upgrade_warning()\n        return False\n\n    def data_received(self, data: bytes) -> None:\n        self._unset_keepalive_if_required()\n\n        self.conn.receive_data(data)\n        self.handle_events()\n\n    def handle_events(self) -> None:\n        while True:\n            try:\n                event = self.conn.next_event()\n            except h11.RemoteProtocolError:\n                msg = \"Invalid HTTP request received.\"\n                self.logger.warning(msg)\n                self.send_400_response(msg)\n                return\n\n            if event is h11.NEED_DATA:\n                break\n\n            elif event is h11.PAUSED:\n                # This case can occur in HTTP pipelining, so we need to\n                # stop reading any more data, and ensure that at the end\n                # of the active request/response cycle we handle any\n                # events that have been buffered up.\n                self.flow.pause_reading()\n                break\n\n            elif isinstance(event, h11.Request):\n                self.headers = [(key.lower(), value) for key, value in event.headers]\n                raw_path, _, query_string = event.target.partition(b\"?\")\n                path = unquote(raw_path.decode(\"ascii\"))\n                full_path = self.root_path + path\n                full_raw_path = self.root_path.encode(\"ascii\") + raw_path\n                self.scope = {\n                    \"type\": \"http\",\n                    \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.3\"},\n                    \"http_version\": event.http_version.decode(\"ascii\"),\n                    \"server\": self.server,\n                    \"client\": self.client,\n                    \"scheme\": self.scheme,  # type: ignore[typeddict-item]\n                    \"method\": event.method.decode(\"ascii\"),\n                    \"root_path\": self.root_path,\n                    \"path\": full_path,\n                    \"raw_path\": full_raw_path,\n                    \"query_string\": query_string,\n                    \"headers\": self.headers,\n                    \"state\": self.app_state.copy(),\n                }\n                if self._should_upgrade():\n                    self.handle_websocket_upgrade(event)\n                    return\n\n                # Handle 503 responses when 'limit_concurrency' is exceeded.\n                if self.limit_concurrency is not None and (\n                    len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency\n                ):\n                    app = service_unavailable\n                    message = \"Exceeded concurrency limit.\"\n                    self.logger.warning(message)\n                else:\n                    app = self.app\n\n                # When starting to process a request, disable the keep-alive\n                # timeout. Normally we disable this when receiving data from\n                # client and set back when finishing processing its request.\n                # However, for pipelined requests processing finishes after\n                # already receiving the next request and thus the timer may\n                # be set here, which we don't want.\n                self._unset_keepalive_if_required()\n\n                self.cycle = RequestResponseCycle(\n                    scope=self.scope,\n                    conn=self.conn,\n                    transport=self.transport,\n                    flow=self.flow,\n                    logger=self.logger,\n                    access_logger=self.access_logger,\n                    access_log=self.access_log,\n                    default_headers=self.server_state.default_headers,\n                    message_event=asyncio.Event(),\n                    on_response=self.on_response_complete,\n                )\n                # For the asyncio loop, we need to explicitly start with an empty context\n                # as it can be polluted from previous ASGI runs.\n                # See https://github.com/python/cpython/issues/140947 for details.\n                task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))\n                # TODO: Replace the line above with the line below for Python >= 3.11\n                # task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())\n                task.add_done_callback(self.tasks.discard)\n                self.tasks.add(task)\n\n            elif isinstance(event, h11.Data):\n                if self.conn.our_state is h11.DONE:\n                    continue\n                self.cycle.body += event.data\n                if len(self.cycle.body) > HIGH_WATER_LIMIT:\n                    self.flow.pause_reading()\n                self.cycle.message_event.set()\n\n            elif isinstance(event, h11.EndOfMessage):\n                if self.conn.our_state is h11.DONE:\n                    self.transport.resume_reading()\n                    self.conn.start_next_cycle()\n                    continue\n                self.cycle.more_body = False\n                self.cycle.message_event.set()\n                if self.conn.their_state == h11.MUST_CLOSE:\n                    break\n\n    def handle_websocket_upgrade(self, event: h11.Request) -> None:\n        if self.logger.level <= TRACE_LOG_LEVEL:  # pragma: full coverage\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sUpgrading to WebSocket\", prefix)\n\n        self.connections.discard(self)\n        output = [event.method, b\" \", event.target, b\" HTTP/1.1\\r\\n\"]\n        for name, value in self.headers:\n            output += [name, b\": \", value, b\"\\r\\n\"]\n        output.append(b\"\\r\\n\")\n        protocol = self.ws_protocol_class(  # type: ignore[call-arg, misc]\n            config=self.config,\n            server_state=self.server_state,\n            app_state=self.app_state,\n        )\n        protocol.connection_made(self.transport)\n        protocol.data_received(b\"\".join(output))\n        self.transport.set_protocol(protocol)\n\n    def send_400_response(self, msg: str) -> None:\n        reason = STATUS_PHRASES[400]\n        headers: list[tuple[bytes, bytes]] = [\n            (b\"content-type\", b\"text/plain; charset=utf-8\"),\n            (b\"connection\", b\"close\"),\n        ]\n        event = h11.Response(status_code=400, headers=headers, reason=reason)\n        output = self.conn.send(event)\n        self.transport.write(output)\n\n        output = self.conn.send(event=h11.Data(data=msg.encode(\"ascii\")))\n        self.transport.write(output)\n\n        output = self.conn.send(event=h11.EndOfMessage())\n        self.transport.write(output)\n\n        self.transport.close()\n\n    def on_response_complete(self) -> None:\n        self.server_state.total_requests += 1\n\n        if self.transport.is_closing():\n            return\n\n        # Set a short Keep-Alive timeout.\n        self._unset_keepalive_if_required()\n\n        self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler)\n\n        # Unpause data reads if needed.\n        self.flow.resume_reading()\n\n        # Unblock any pipelined events.\n        if self.conn.our_state is h11.DONE and self.conn.their_state is h11.DONE:\n            self.conn.start_next_cycle()\n            self.handle_events()\n\n    def shutdown(self) -> None:\n        \"\"\"\n        Called by the server to commence a graceful shutdown.\n        \"\"\"\n        if self.cycle is None or self.cycle.response_complete:\n            event = h11.ConnectionClosed()\n            self.conn.send(event)\n            self.transport.close()\n        else:\n            self.cycle.keep_alive = False\n\n    def pause_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer exceeds the high water mark.\n        \"\"\"\n        self.flow.pause_writing()  # pragma: full coverage\n\n    def resume_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer drops below the low water mark.\n        \"\"\"\n        self.flow.resume_writing()  # pragma: full coverage\n\n    def timeout_keep_alive_handler(self) -> None:\n        \"\"\"\n        Called on a keep-alive connection if no new data is received after a short\n        delay.\n        \"\"\"\n        if not self.transport.is_closing():\n            event = h11.ConnectionClosed()\n            self.conn.send(event)\n            self.transport.close()\n\n\nclass RequestResponseCycle:\n    def __init__(\n        self,\n        scope: HTTPScope,\n        conn: h11.Connection,\n        transport: asyncio.Transport,\n        flow: FlowControl,\n        logger: logging.Logger,\n        access_logger: logging.Logger,\n        access_log: bool,\n        default_headers: list[tuple[bytes, bytes]],\n        message_event: asyncio.Event,\n        on_response: Callable[..., None],\n    ) -> None:\n        self.scope = scope\n        self.conn = conn\n        self.transport = transport\n        self.flow = flow\n        self.logger = logger\n        self.access_logger = access_logger\n        self.access_log = access_log\n        self.default_headers = default_headers\n        self.message_event = message_event\n        self.on_response = on_response\n\n        # Connection state\n        self.disconnected = False\n        self.keep_alive = True\n        self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue\n\n        # Request state\n        self.body = bytearray()\n        self.more_body = True\n\n        # Response state\n        self.response_started = False\n        self.response_complete = False\n\n    # ASGI exception wrapper\n    async def run_asgi(self, app: ASGI3Application) -> None:\n        try:\n            result = await app(  # type: ignore[func-returns-value]\n                self.scope, self.receive, self.send\n            )\n        except BaseException as exc:\n            msg = \"Exception in ASGI application\\n\"\n            self.logger.error(msg, exc_info=exc)\n            if not self.response_started:\n                await self.send_500_response()\n            else:\n                self.transport.close()\n        else:\n            if result is not None:\n                msg = \"ASGI callable should return None, but returned '%s'.\"\n                self.logger.error(msg, result)\n                self.transport.close()\n            elif not self.response_started and not self.disconnected:\n                msg = \"ASGI callable returned without starting response.\"\n                self.logger.error(msg)\n                await self.send_500_response()\n            elif not self.response_complete and not self.disconnected:\n                msg = \"ASGI callable returned without completing response.\"\n                self.logger.error(msg)\n                self.transport.close()\n        finally:\n            self.on_response = lambda: None\n\n    async def send_500_response(self) -> None:\n        response_start_event: HTTPResponseStartEvent = {\n            \"type\": \"http.response.start\",\n            \"status\": 500,\n            \"headers\": [\n                (b\"content-type\", b\"text/plain; charset=utf-8\"),\n                (b\"connection\", b\"close\"),\n            ],\n        }\n        await self.send(response_start_event)\n        response_body_event: HTTPResponseBodyEvent = {\n            \"type\": \"http.response.body\",\n            \"body\": b\"Internal Server Error\",\n            \"more_body\": False,\n        }\n        await self.send(response_body_event)\n\n    # ASGI interface\n    async def send(self, message: ASGISendEvent) -> None:\n        message_type = message[\"type\"]\n\n        if self.flow.write_paused and not self.disconnected:\n            await self.flow.drain()  # pragma: full coverage\n\n        if self.disconnected:\n            return  # pragma: full coverage\n\n        if not self.response_started:\n            # Sending response status line and headers\n            if message_type != \"http.response.start\":\n                msg = \"Expected ASGI message 'http.response.start', but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n            message = cast(\"HTTPResponseStartEvent\", message)\n\n            self.response_started = True\n            self.waiting_for_100_continue = False\n\n            status = message[\"status\"]\n            headers = self.default_headers + list(message.get(\"headers\", []))\n\n            if CLOSE_HEADER in self.scope[\"headers\"] and CLOSE_HEADER not in headers:\n                headers = headers + [CLOSE_HEADER]\n\n            if self.access_log:\n                self.access_logger.info(\n                    '%s - \"%s %s HTTP/%s\" %d',\n                    get_client_addr(self.scope),\n                    self.scope[\"method\"],\n                    get_path_with_query_string(self.scope),\n                    self.scope[\"http_version\"],\n                    status,\n                )\n\n            # Write response status line and headers\n            reason = STATUS_PHRASES[status]\n            response = h11.Response(status_code=status, headers=headers, reason=reason)\n            output = self.conn.send(event=response)\n            self.transport.write(output)\n\n        elif not self.response_complete:\n            # Sending response body\n            if message_type != \"http.response.body\":\n                msg = \"Expected ASGI message 'http.response.body', but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n            message = cast(\"HTTPResponseBodyEvent\", message)\n\n            body = message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n\n            # Write response body\n            data = b\"\" if self.scope[\"method\"] == \"HEAD\" else body\n            output = self.conn.send(event=h11.Data(data=data))\n            self.transport.write(output)\n\n            # Handle response completion\n            if not more_body:\n                self.response_complete = True\n                self.message_event.set()\n                output = self.conn.send(event=h11.EndOfMessage())\n                self.transport.write(output)\n\n        else:\n            # Response already sent\n            msg = \"Unexpected ASGI message '%s' sent, after response already completed.\"\n            raise RuntimeError(msg % message_type)\n\n        if self.response_complete:\n            if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive:\n                self.conn.send(event=h11.ConnectionClosed())\n                self.transport.close()\n            self.on_response()\n\n    async def receive(self) -> ASGIReceiveEvent:\n        if self.waiting_for_100_continue and not self.transport.is_closing():\n            headers: list[tuple[str, str]] = []\n            event = h11.InformationalResponse(status_code=100, headers=headers, reason=\"Continue\")\n            output = self.conn.send(event=event)\n            self.transport.write(output)\n            self.waiting_for_100_continue = False\n\n        if not self.disconnected and not self.response_complete:\n            self.flow.resume_reading()\n            await self.message_event.wait()\n            self.message_event.clear()\n\n        if self.disconnected or self.response_complete:\n            return {\"type\": \"http.disconnect\"}\n\n        message: HTTPRequestEvent = {\n            \"type\": \"http.request\",\n            \"body\": bytes(self.body),\n            \"more_body\": self.more_body,\n        }\n        self.body = bytearray()\n        return message\n"
  },
  {
    "path": "uvicorn/protocols/http/httptools_impl.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport contextvars\nimport http\nimport logging\nimport re\nimport urllib\nfrom asyncio.events import TimerHandle\nfrom collections import deque\nfrom collections.abc import Callable\nfrom typing import Any, Literal, cast\n\nimport httptools\n\nfrom uvicorn._types import (\n    ASGI3Application,\n    ASGIReceiveEvent,\n    ASGISendEvent,\n    HTTPRequestEvent,\n    HTTPResponseStartEvent,\n    HTTPScope,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable\nfrom uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl\nfrom uvicorn.server import ServerState\n\nHEADER_RE = re.compile(b'[\\x00-\\x1f\\x7f()<>@,;:\\\\[\\\\]={} \\t\\\\\\\\\"]')\nHEADER_VALUE_RE = re.compile(b\"[\\x00-\\x08\\x0a-\\x1f\\x7f]\")\n\n\ndef _get_status_line(status_code: int) -> bytes:\n    try:\n        phrase = http.HTTPStatus(status_code).phrase.encode()\n    except ValueError:\n        phrase = b\"\"\n    return b\"\".join([b\"HTTP/1.1 \", str(status_code).encode(), b\" \", phrase, b\"\\r\\n\"])\n\n\nSTATUS_LINE = {status_code: _get_status_line(status_code) for status_code in range(100, 600)}\n\n\nclass HttpToolsProtocol(asyncio.Protocol):\n    def __init__(\n        self,\n        config: Config,\n        server_state: ServerState,\n        app_state: dict[str, Any],\n        _loop: asyncio.AbstractEventLoop | None = None,\n    ) -> None:\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.app = config.loaded_app\n        self.loop = _loop or asyncio.get_event_loop()\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.access_logger = logging.getLogger(\"uvicorn.access\")\n        self.access_log = self.access_logger.hasHandlers()\n        self.parser = httptools.HttpRequestParser(self)\n\n        try:\n            # Enable dangerous leniencies to allow server to a response on the first request from a pipelined request.\n            self.parser.set_dangerous_leniencies(lenient_data_after_close=True)\n        except AttributeError:  # pragma: no cover\n            # httptools < 0.6.3\n            pass\n\n        self.ws_protocol_class = config.ws_protocol_class\n        self.root_path = config.root_path\n        self.limit_concurrency = config.limit_concurrency\n        self.app_state = app_state\n\n        # Timeouts\n        self.timeout_keep_alive_task: TimerHandle | None = None\n        self.timeout_keep_alive = config.timeout_keep_alive\n\n        # Global state\n        self.server_state = server_state\n        self.connections = server_state.connections\n        self.tasks = server_state.tasks\n\n        # Per-connection state\n        self.transport: asyncio.Transport = None  # type: ignore[assignment]\n        self.flow: FlowControl = None  # type: ignore[assignment]\n        self.server: tuple[str, int | None] | None = None\n        self.client: tuple[str, int] | None = None\n        self.scheme: Literal[\"http\", \"https\"] | None = None\n        self.pipeline: deque[tuple[RequestResponseCycle, ASGI3Application]] = deque()\n\n        # Per-request state\n        self.scope: HTTPScope = None  # type: ignore[assignment]\n        self.headers: list[tuple[bytes, bytes]] = None  # type: ignore[assignment]\n        self.expect_100_continue = False\n        self.cycle: RequestResponseCycle = None  # type: ignore[assignment]\n\n    # Protocol interface\n    def connection_made(  # type: ignore[override]\n        self, transport: asyncio.Transport\n    ) -> None:\n        self.connections.add(self)\n\n        self.transport = transport\n        self.flow = FlowControl(transport)\n        self.server = get_local_addr(transport)\n        self.client = get_remote_addr(transport)\n        self.scheme = \"https\" if is_ssl(transport) else \"http\"\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sHTTP connection made\", prefix)\n\n    def connection_lost(self, exc: Exception | None) -> None:\n        self.connections.discard(self)\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sHTTP connection lost\", prefix)\n\n        if self.cycle and not self.cycle.response_complete:\n            self.cycle.disconnected = True\n        if self.cycle is not None:\n            self.cycle.message_event.set()\n        if self.flow is not None:\n            self.flow.resume_writing()\n        if exc is None:\n            self.transport.close()\n            self._unset_keepalive_if_required()\n\n        self.parser = None\n\n    def eof_received(self) -> None:\n        pass\n\n    def _unset_keepalive_if_required(self) -> None:\n        if self.timeout_keep_alive_task is not None:\n            self.timeout_keep_alive_task.cancel()\n            self.timeout_keep_alive_task = None\n\n    def _get_upgrade(self) -> bytes | None:\n        connection = []\n        upgrade = None\n        for name, value in self.headers:\n            if name == b\"connection\":\n                connection = [token.lower().strip() for token in value.split(b\",\")]\n            if name == b\"upgrade\":\n                upgrade = value.lower()\n        if b\"upgrade\" in connection:\n            return upgrade\n        return None  # pragma: full coverage\n\n    def _should_upgrade_to_ws(self) -> bool:\n        if self.ws_protocol_class is None:\n            return False\n        return True\n\n    def _unsupported_upgrade_warning(self) -> None:\n        self.logger.warning(\"Unsupported upgrade request.\")\n        if not self._should_upgrade_to_ws():\n            msg = \"No supported WebSocket library detected. Please use \\\"pip install 'uvicorn[standard]'\\\", or install 'websockets' or 'wsproto' manually.\"  # noqa: E501\n            self.logger.warning(msg)\n\n    def _should_upgrade(self) -> bool:\n        upgrade = self._get_upgrade()\n        return upgrade == b\"websocket\" and self._should_upgrade_to_ws()\n\n    def data_received(self, data: bytes) -> None:\n        self._unset_keepalive_if_required()\n\n        try:\n            self.parser.feed_data(data)\n        except httptools.HttpParserError:\n            msg = \"Invalid HTTP request received.\"\n            self.logger.warning(msg)\n            self.send_400_response(msg)\n            return\n        except httptools.HttpParserUpgrade:\n            if self._should_upgrade():\n                self.handle_websocket_upgrade()\n            else:\n                self._unsupported_upgrade_warning()\n\n    def handle_websocket_upgrade(self) -> None:\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sUpgrading to WebSocket\", prefix)\n\n        self.connections.discard(self)\n        method = self.scope[\"method\"].encode()\n        output = [method, b\" \", self.url, b\" HTTP/1.1\\r\\n\"]\n        for name, value in self.scope[\"headers\"]:\n            output += [name, b\": \", value, b\"\\r\\n\"]\n        output.append(b\"\\r\\n\")\n        protocol = self.ws_protocol_class(  # type: ignore[call-arg, misc]\n            config=self.config,\n            server_state=self.server_state,\n            app_state=self.app_state,\n        )\n        protocol.connection_made(self.transport)\n        protocol.data_received(b\"\".join(output))\n        self.transport.set_protocol(protocol)\n\n    def send_400_response(self, msg: str) -> None:\n        content = [STATUS_LINE[400]]\n        for name, value in self.server_state.default_headers:\n            content.extend([name, b\": \", value, b\"\\r\\n\"])  # pragma: full coverage\n        content.extend(\n            [\n                b\"content-type: text/plain; charset=utf-8\\r\\n\",\n                b\"content-length: \" + str(len(msg)).encode(\"ascii\") + b\"\\r\\n\",\n                b\"connection: close\\r\\n\",\n                b\"\\r\\n\",\n                msg.encode(\"ascii\"),\n            ]\n        )\n        self.transport.write(b\"\".join(content))\n        self.transport.close()\n\n    def on_message_begin(self) -> None:\n        self.url = b\"\"\n        self.expect_100_continue = False\n        self.headers = []\n        self.scope = {  # type: ignore[typeddict-item]\n            \"type\": \"http\",\n            \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.3\"},\n            \"http_version\": \"1.1\",\n            \"server\": self.server,\n            \"client\": self.client,\n            \"scheme\": self.scheme,  # type: ignore[typeddict-item]\n            \"root_path\": self.root_path,\n            \"headers\": self.headers,\n            \"state\": self.app_state.copy(),\n        }\n\n    # Parser callbacks\n    def on_url(self, url: bytes) -> None:\n        self.url += url\n\n    def on_header(self, name: bytes, value: bytes) -> None:\n        name = name.lower()\n        if name == b\"expect\" and value.lower() == b\"100-continue\":\n            self.expect_100_continue = True\n        self.headers.append((name, value))\n\n    def on_headers_complete(self) -> None:\n        http_version = self.parser.get_http_version()\n        method = self.parser.get_method()\n        self.scope[\"method\"] = method.decode(\"ascii\")\n        if http_version != \"1.1\":\n            self.scope[\"http_version\"] = http_version\n        if self.parser.should_upgrade() and self._should_upgrade():\n            return\n        parsed_url = httptools.parse_url(self.url)\n        raw_path = parsed_url.path\n        path = raw_path.decode(\"ascii\")\n        if \"%\" in path:\n            path = urllib.parse.unquote(path)\n        full_path = self.root_path + path\n        full_raw_path = self.root_path.encode(\"ascii\") + raw_path\n        self.scope[\"path\"] = full_path\n        self.scope[\"raw_path\"] = full_raw_path\n        self.scope[\"query_string\"] = parsed_url.query or b\"\"\n\n        # Handle 503 responses when 'limit_concurrency' is exceeded.\n        if self.limit_concurrency is not None and (\n            len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency\n        ):\n            app = service_unavailable\n            message = \"Exceeded concurrency limit.\"\n            self.logger.warning(message)\n        else:\n            app = self.app\n\n        existing_cycle = self.cycle\n        self.cycle = RequestResponseCycle(\n            scope=self.scope,\n            transport=self.transport,\n            flow=self.flow,\n            logger=self.logger,\n            access_logger=self.access_logger,\n            access_log=self.access_log,\n            default_headers=self.server_state.default_headers,\n            message_event=asyncio.Event(),\n            expect_100_continue=self.expect_100_continue,\n            keep_alive=http_version != \"1.0\",\n            on_response=self.on_response_complete,\n        )\n        if existing_cycle is None or existing_cycle.response_complete:\n            # Standard case - start processing the request.\n            # For the asyncio loop, we need to explicitly start with an empty context\n            # as it can be polluted from previous ASGI runs.\n            # See https://github.com/python/cpython/issues/140947 for details.\n            task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))\n            # TODO: Replace the line above with the line below for Python >= 3.11\n            # task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())\n            task.add_done_callback(self.tasks.discard)\n            self.tasks.add(task)\n        else:\n            # Pipelined HTTP requests need to be queued up.\n            self.flow.pause_reading()\n            self.pipeline.appendleft((self.cycle, app))\n\n    def on_body(self, body: bytes) -> None:\n        if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:\n            return\n        self.cycle.body += body\n        if len(self.cycle.body) > HIGH_WATER_LIMIT:\n            self.flow.pause_reading()\n        self.cycle.message_event.set()\n\n    def on_message_complete(self) -> None:\n        if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:\n            return\n        self.cycle.more_body = False\n        self.cycle.message_event.set()\n\n    def on_response_complete(self) -> None:\n        # Callback for pipelined HTTP requests to be started.\n        self.server_state.total_requests += 1\n\n        if self.transport.is_closing():\n            return\n\n        self._unset_keepalive_if_required()\n\n        # Unpause data reads if needed.\n        self.flow.resume_reading()\n\n        # Unblock any pipelined events. If there are none, arm the\n        # Keep-Alive timeout instead.\n        if self.pipeline:\n            cycle, app = self.pipeline.pop()\n            task = self.loop.create_task(cycle.run_asgi(app))\n            task.add_done_callback(self.tasks.discard)\n            self.tasks.add(task)\n        else:\n            self.timeout_keep_alive_task = self.loop.call_later(\n                self.timeout_keep_alive, self.timeout_keep_alive_handler\n            )\n\n    def shutdown(self) -> None:\n        \"\"\"\n        Called by the server to commence a graceful shutdown.\n        \"\"\"\n        if self.cycle is None or self.cycle.response_complete:\n            self.transport.close()\n        else:\n            self.cycle.keep_alive = False\n\n    def pause_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer exceeds the high water mark.\n        \"\"\"\n        self.flow.pause_writing()  # pragma: full coverage\n\n    def resume_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer drops below the low water mark.\n        \"\"\"\n        self.flow.resume_writing()  # pragma: full coverage\n\n    def timeout_keep_alive_handler(self) -> None:\n        \"\"\"\n        Called on a keep-alive connection if no new data is received after a short\n        delay.\n        \"\"\"\n        if not self.transport.is_closing():\n            self.transport.close()\n\n\nclass RequestResponseCycle:\n    def __init__(\n        self,\n        scope: HTTPScope,\n        transport: asyncio.Transport,\n        flow: FlowControl,\n        logger: logging.Logger,\n        access_logger: logging.Logger,\n        access_log: bool,\n        default_headers: list[tuple[bytes, bytes]],\n        message_event: asyncio.Event,\n        expect_100_continue: bool,\n        keep_alive: bool,\n        on_response: Callable[..., None],\n    ):\n        self.scope = scope\n        self.transport = transport\n        self.flow = flow\n        self.logger = logger\n        self.access_logger = access_logger\n        self.access_log = access_log\n        self.default_headers = default_headers\n        self.message_event = message_event\n        self.on_response = on_response\n\n        # Connection state\n        self.disconnected = False\n        self.keep_alive = keep_alive\n        self.waiting_for_100_continue = expect_100_continue\n\n        # Request state\n        self.body = bytearray()\n        self.more_body = True\n\n        # Response state\n        self.response_started = False\n        self.response_complete = False\n        self.chunked_encoding: bool | None = None\n        self.expected_content_length = 0\n\n    # ASGI exception wrapper\n    async def run_asgi(self, app: ASGI3Application) -> None:\n        try:\n            result = await app(  # type: ignore[func-returns-value]\n                self.scope, self.receive, self.send\n            )\n        except BaseException as exc:\n            msg = \"Exception in ASGI application\\n\"\n            self.logger.error(msg, exc_info=exc)\n            if not self.response_started:\n                await self.send_500_response()\n            else:\n                self.transport.close()\n        else:\n            if result is not None:\n                msg = \"ASGI callable should return None, but returned '%s'.\"\n                self.logger.error(msg, result)\n                self.transport.close()\n            elif not self.response_started and not self.disconnected:\n                msg = \"ASGI callable returned without starting response.\"\n                self.logger.error(msg)\n                await self.send_500_response()\n            elif not self.response_complete and not self.disconnected:\n                msg = \"ASGI callable returned without completing response.\"\n                self.logger.error(msg)\n                self.transport.close()\n        finally:\n            self.on_response = lambda: None\n\n    async def send_500_response(self) -> None:\n        await self.send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": 500,\n                \"headers\": [\n                    (b\"content-type\", b\"text/plain; charset=utf-8\"),\n                    (b\"content-length\", b\"21\"),\n                    (b\"connection\", b\"close\"),\n                ],\n            }\n        )\n        await self.send({\"type\": \"http.response.body\", \"body\": b\"Internal Server Error\", \"more_body\": False})\n\n    # ASGI interface\n    async def send(self, message: ASGISendEvent) -> None:\n        message_type = message[\"type\"]\n\n        if self.flow.write_paused and not self.disconnected:\n            await self.flow.drain()  # pragma: full coverage\n\n        if self.disconnected:\n            return  # pragma: full coverage\n\n        if not self.response_started:\n            # Sending response status line and headers\n            if message_type != \"http.response.start\":\n                msg = \"Expected ASGI message 'http.response.start', but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n            message = cast(\"HTTPResponseStartEvent\", message)\n\n            self.response_started = True\n            self.waiting_for_100_continue = False\n\n            status_code = message[\"status\"]\n            headers = self.default_headers + list(message.get(\"headers\", []))\n\n            if CLOSE_HEADER in self.scope[\"headers\"] and CLOSE_HEADER not in headers:\n                headers = headers + [CLOSE_HEADER]\n\n            if self.access_log:\n                self.access_logger.info(\n                    '%s - \"%s %s HTTP/%s\" %d',\n                    get_client_addr(self.scope),\n                    self.scope[\"method\"],\n                    get_path_with_query_string(self.scope),\n                    self.scope[\"http_version\"],\n                    status_code,\n                )\n\n            # Write response status line and headers\n            content = [STATUS_LINE[status_code]]\n\n            for name, value in headers:\n                if HEADER_RE.search(name):\n                    raise RuntimeError(\"Invalid HTTP header name.\")  # pragma: full coverage\n                if HEADER_VALUE_RE.search(value):\n                    raise RuntimeError(\"Invalid HTTP header value.\")\n\n                name = name.lower()\n                if name == b\"content-length\" and self.chunked_encoding is None:\n                    self.expected_content_length = int(value.decode())\n                    self.chunked_encoding = False\n                elif name == b\"transfer-encoding\" and value.lower() == b\"chunked\":\n                    self.expected_content_length = 0\n                    self.chunked_encoding = True\n                elif name == b\"connection\" and value.lower() == b\"close\":\n                    self.keep_alive = False\n                content.extend([name, b\": \", value, b\"\\r\\n\"])\n\n            if self.chunked_encoding is None and self.scope[\"method\"] != \"HEAD\" and status_code not in (204, 304):\n                # Neither content-length nor transfer-encoding specified\n                self.chunked_encoding = True\n                content.append(b\"transfer-encoding: chunked\\r\\n\")\n\n            content.append(b\"\\r\\n\")\n            self.transport.write(b\"\".join(content))\n\n        elif not self.response_complete:\n            # Sending response body\n            if message_type != \"http.response.body\":\n                msg = \"Expected ASGI message 'http.response.body', but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n\n            body = cast(bytes, message.get(\"body\", b\"\"))\n            more_body = message.get(\"more_body\", False)\n\n            # Write response body\n            if self.scope[\"method\"] == \"HEAD\":\n                self.expected_content_length = 0\n            elif self.chunked_encoding:\n                if body:\n                    content = [b\"%x\\r\\n\" % len(body), body, b\"\\r\\n\"]\n                else:\n                    content = []\n                if not more_body:\n                    content.append(b\"0\\r\\n\\r\\n\")\n                self.transport.write(b\"\".join(content))\n            else:\n                num_bytes = len(body)\n                if num_bytes > self.expected_content_length:\n                    raise RuntimeError(\"Response content longer than Content-Length\")\n                else:\n                    self.expected_content_length -= num_bytes\n                self.transport.write(body)\n\n            # Handle response completion\n            if not more_body:\n                if self.expected_content_length != 0:\n                    raise RuntimeError(\"Response content shorter than Content-Length\")\n                self.response_complete = True\n                self.message_event.set()\n                if not self.keep_alive:\n                    self.transport.close()\n                self.on_response()\n\n        else:\n            # Response already sent\n            msg = \"Unexpected ASGI message '%s' sent, after response already completed.\"\n            raise RuntimeError(msg % message_type)\n\n    async def receive(self) -> ASGIReceiveEvent:\n        if self.waiting_for_100_continue and not self.transport.is_closing():\n            self.transport.write(b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n            self.waiting_for_100_continue = False\n\n        if not self.disconnected and not self.response_complete:\n            self.flow.resume_reading()\n            await self.message_event.wait()\n            self.message_event.clear()\n\n        if self.disconnected or self.response_complete:\n            return {\"type\": \"http.disconnect\"}\n        message: HTTPRequestEvent = {\"type\": \"http.request\", \"body\": bytes(self.body), \"more_body\": self.more_body}\n        self.body = bytearray()\n        return message\n"
  },
  {
    "path": "uvicorn/protocols/utils.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport socket\nimport urllib.parse\n\nfrom uvicorn._types import WWWScope\n\n\nclass ClientDisconnected(OSError): ...\n\n\ndef get_remote_addr(transport: asyncio.Transport) -> tuple[str, int] | None:\n    socket_info: socket.socket | None = transport.get_extra_info(\"socket\")\n    if socket_info is not None:\n        try:\n            info = socket_info.getpeername()\n            return (str(info[0]), int(info[1])) if isinstance(info, tuple) else None\n        except OSError:  # pragma: no cover\n            # This case appears to inconsistently occur with uvloop\n            # bound to a unix domain socket.\n            return None\n\n    info = transport.get_extra_info(\"peername\")\n    if info is not None and isinstance(info, list | tuple) and len(info) == 2:\n        return (str(info[0]), int(info[1]))\n    return None\n\n\ndef get_local_addr(transport: asyncio.Transport) -> tuple[str, int | None] | None:\n    socket_info: socket.socket | None = transport.get_extra_info(\"socket\")\n    if socket_info is not None:\n        info = socket_info.getsockname()\n        if isinstance(info, tuple):\n            return (str(info[0]), int(info[1]))\n        if isinstance(info, str):\n            return (info, None)\n        return None\n    info = transport.get_extra_info(\"sockname\")\n    if info is not None and isinstance(info, list | tuple) and len(info) == 2:\n        return (str(info[0]), int(info[1]))\n    if isinstance(info, str):\n        return (info, None)\n    return None\n\n\ndef is_ssl(transport: asyncio.Transport) -> bool:\n    return bool(transport.get_extra_info(\"sslcontext\"))\n\n\ndef get_client_addr(scope: WWWScope) -> str:\n    client = scope.get(\"client\")\n    if not client:\n        return \"\"\n    return \"%s:%d\" % client\n\n\ndef get_path_with_query_string(scope: WWWScope) -> str:\n    path_with_query_string = urllib.parse.quote(scope[\"path\"])\n    if scope[\"query_string\"]:\n        path_with_query_string = \"{}?{}\".format(path_with_query_string, scope[\"query_string\"].decode(\"ascii\"))\n    return path_with_query_string\n"
  },
  {
    "path": "uvicorn/protocols/websockets/__init__.py",
    "content": ""
  },
  {
    "path": "uvicorn/protocols/websockets/auto.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\nAutoWebSocketsProtocol: Callable[..., asyncio.Protocol] | None\ntry:\n    import websockets  # noqa\nexcept ImportError:  # pragma: no cover\n    try:\n        import wsproto  # noqa\n    except ImportError:\n        AutoWebSocketsProtocol = None\n    else:\n        from uvicorn.protocols.websockets.wsproto_impl import WSProtocol\n\n        AutoWebSocketsProtocol = WSProtocol\nelse:\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n\n    AutoWebSocketsProtocol = WebSocketProtocol\n"
  },
  {
    "path": "uvicorn/protocols/websockets/websockets_impl.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport http\nimport logging\nfrom collections.abc import Sequence\nfrom typing import Any, Literal, cast\nfrom urllib.parse import unquote\n\nimport websockets\nimport websockets.legacy.handshake\nfrom websockets.datastructures import Headers\nfrom websockets.exceptions import ConnectionClosed\nfrom websockets.extensions.base import ServerExtensionFactory\nfrom websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory\nfrom websockets.legacy.server import HTTPResponse\nfrom websockets.server import WebSocketServerProtocol\nfrom websockets.typing import Subprotocol\n\nfrom uvicorn._types import (\n    ASGI3Application,\n    ASGISendEvent,\n    WebSocketAcceptEvent,\n    WebSocketCloseEvent,\n    WebSocketConnectEvent,\n    WebSocketDisconnectEvent,\n    WebSocketReceiveEvent,\n    WebSocketResponseBodyEvent,\n    WebSocketResponseStartEvent,\n    WebSocketScope,\n    WebSocketSendEvent,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.protocols.utils import (\n    ClientDisconnected,\n    get_client_addr,\n    get_local_addr,\n    get_path_with_query_string,\n    get_remote_addr,\n    is_ssl,\n)\nfrom uvicorn.server import ServerState\n\n\nclass Server:\n    closing = False\n\n    def register(self, ws: WebSocketServerProtocol) -> None:\n        pass\n\n    def unregister(self, ws: WebSocketServerProtocol) -> None:\n        pass\n\n    def is_serving(self) -> bool:\n        return not self.closing\n\n\nclass WebSocketProtocol(WebSocketServerProtocol):\n    extra_headers: list[tuple[str, str]]\n    logger: logging.Logger | logging.LoggerAdapter[Any]\n\n    def __init__(\n        self,\n        config: Config,\n        server_state: ServerState,\n        app_state: dict[str, Any],\n        _loop: asyncio.AbstractEventLoop | None = None,\n    ):\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.app = cast(ASGI3Application, config.loaded_app)\n        self.loop = _loop or asyncio.get_event_loop()\n        self.root_path = config.root_path\n        self.app_state = app_state\n\n        # Shared server state\n        self.connections = server_state.connections\n        self.tasks = server_state.tasks\n\n        # Connection state\n        self.transport: asyncio.Transport = None  # type: ignore[assignment]\n        self.server: tuple[str, int | None] | None = None\n        self.client: tuple[str, int] | None = None\n        self.scheme: Literal[\"wss\", \"ws\"] = None  # type: ignore[assignment]\n\n        # Connection events\n        self.scope: WebSocketScope\n        self.handshake_started_event = asyncio.Event()\n        self.handshake_completed_event = asyncio.Event()\n        self.closed_event = asyncio.Event()\n        self.initial_response: HTTPResponse | None = None\n        self.connect_sent = False\n        self.lost_connection_before_handshake = False\n        self.accepted_subprotocol: Subprotocol | None = None\n\n        self.ws_server: Server = Server()  # type: ignore[assignment]\n\n        extensions: list[ServerExtensionFactory] = []\n        if self.config.ws_per_message_deflate:\n            extensions.append(ServerPerMessageDeflateFactory())\n\n        super().__init__(\n            ws_handler=self.ws_handler,\n            ws_server=self.ws_server,  # type: ignore[arg-type]\n            max_size=self.config.ws_max_size,\n            max_queue=self.config.ws_max_queue,\n            ping_interval=self.config.ws_ping_interval,\n            ping_timeout=self.config.ws_ping_timeout,\n            extensions=extensions,\n            logger=logging.getLogger(\"uvicorn.error\"),\n        )\n        self.server_header = None\n        self.extra_headers = [\n            (name.decode(\"latin-1\"), value.decode(\"latin-1\")) for name, value in server_state.default_headers\n        ]\n\n    def connection_made(  # type: ignore[override]\n        self, transport: asyncio.Transport\n    ) -> None:\n        self.connections.add(self)\n        self.transport = transport\n        self.server = get_local_addr(transport)\n        self.client = get_remote_addr(transport)\n        self.scheme = \"wss\" if is_ssl(transport) else \"ws\"\n\n        if self.logger.isEnabledFor(TRACE_LOG_LEVEL):\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection made\", prefix)\n\n        super().connection_made(transport)\n\n    def connection_lost(self, exc: Exception | None) -> None:\n        self.connections.remove(self)\n\n        if self.logger.isEnabledFor(TRACE_LOG_LEVEL):\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection lost\", prefix)\n\n        self.lost_connection_before_handshake = not self.handshake_completed_event.is_set()\n        self.handshake_completed_event.set()\n        super().connection_lost(exc)\n        if exc is None:\n            self.transport.close()\n\n    def shutdown(self) -> None:\n        self.ws_server.closing = True\n        if self.handshake_completed_event.is_set():\n            self.fail_connection(1012)\n        else:\n            self.send_500_response()\n        self.transport.close()\n\n    def on_task_complete(self, task: asyncio.Task[None]) -> None:\n        self.tasks.discard(task)\n\n    async def process_request(self, path: str, request_headers: Headers) -> HTTPResponse | None:\n        \"\"\"\n        This hook is called to determine if the websocket should return\n        an HTTP response and close.\n\n        Our behavior here is to start the ASGI application, and then wait\n        for either `accept` or `close` in order to determine if we should\n        close the connection.\n        \"\"\"\n        path_portion, _, query_string = path.partition(\"?\")\n\n        websockets.legacy.handshake.check_request(request_headers)\n\n        subprotocols: list[str] = []\n        for header in request_headers.get_all(\"Sec-WebSocket-Protocol\"):\n            subprotocols.extend([token.strip() for token in header.split(\",\")])\n\n        asgi_headers = [\n            (name.encode(\"ascii\"), value.encode(\"ascii\", errors=\"surrogateescape\"))\n            for name, value in request_headers.raw_items()\n        ]\n        path = unquote(path_portion)\n        full_path = self.root_path + path\n        full_raw_path = self.root_path.encode(\"ascii\") + path_portion.encode(\"ascii\")\n\n        self.scope = {\n            \"type\": \"websocket\",\n            \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.4\"},\n            \"http_version\": \"1.1\",\n            \"scheme\": self.scheme,\n            \"server\": self.server,\n            \"client\": self.client,\n            \"root_path\": self.root_path,\n            \"path\": full_path,\n            \"raw_path\": full_raw_path,\n            \"query_string\": query_string.encode(\"ascii\"),\n            \"headers\": asgi_headers,\n            \"subprotocols\": subprotocols,\n            \"state\": self.app_state.copy(),\n            \"extensions\": {\"websocket.http.response\": {}},\n        }\n        task = self.loop.create_task(self.run_asgi())\n        task.add_done_callback(self.on_task_complete)\n        self.tasks.add(task)\n        await self.handshake_started_event.wait()\n        return self.initial_response\n\n    def process_subprotocol(\n        self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None\n    ) -> Subprotocol | None:\n        \"\"\"\n        We override the standard 'process_subprotocol' behavior here so that\n        we return whatever subprotocol is sent in the 'accept' message.\n        \"\"\"\n        return self.accepted_subprotocol\n\n    def send_500_response(self) -> None:\n        msg = b\"Internal Server Error\"\n        content = [\n            b\"HTTP/1.1 500 Internal Server Error\\r\\ncontent-type: text/plain; charset=utf-8\\r\\n\",\n            b\"content-length: \" + str(len(msg)).encode(\"ascii\") + b\"\\r\\n\",\n            b\"connection: close\\r\\n\",\n            b\"\\r\\n\",\n            msg,\n        ]\n        self.transport.write(b\"\".join(content))\n        # Allow handler task to terminate cleanly, as websockets doesn't cancel it by\n        # itself (see https://github.com/Kludex/uvicorn/issues/920)\n        self.handshake_started_event.set()\n\n    async def ws_handler(self, protocol: WebSocketServerProtocol, path: str) -> Any:  # type: ignore[override]\n        \"\"\"\n        This is the main handler function for the 'websockets' implementation\n        to call into. We just wait for close then return, and instead allow\n        'send' and 'receive' events to drive the flow.\n        \"\"\"\n        self.handshake_completed_event.set()\n        await self.wait_closed()\n\n    async def run_asgi(self) -> None:\n        \"\"\"\n        Wrapper around the ASGI callable, handling exceptions and unexpected\n        termination states.\n        \"\"\"\n        try:\n            result = await self.app(self.scope, self.asgi_receive, self.asgi_send)  # type: ignore[func-returns-value]\n        except ClientDisconnected:  # pragma: full coverage\n            self.closed_event.set()\n        except BaseException:\n            self.closed_event.set()\n            self.logger.exception(\"Exception in ASGI application\\n\")\n            if not self.handshake_started_event.is_set():\n                self.send_500_response()\n            else:\n                await self.handshake_completed_event.wait()\n        else:\n            self.closed_event.set()\n            if not self.handshake_started_event.is_set():\n                self.logger.error(\"ASGI callable returned without sending handshake.\")\n                self.send_500_response()\n            elif result is not None:\n                self.logger.error(\"ASGI callable should return None, but returned '%s'.\", result)\n            await self.handshake_completed_event.wait()\n        self.transport.close()\n\n    async def asgi_send(self, message: ASGISendEvent) -> None:\n        message_type = message[\"type\"]\n\n        if not self.handshake_started_event.is_set():\n            if message_type == \"websocket.accept\":\n                message = cast(\"WebSocketAcceptEvent\", message)\n                self.logger.info(\n                    '%s - \"WebSocket %s\" [accepted]',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                self.initial_response = None\n                self.accepted_subprotocol = cast(Subprotocol | None, message.get(\"subprotocol\"))\n                if \"headers\" in message:\n                    self.extra_headers.extend(\n                        # ASGI spec requires bytes\n                        # But for compatibility we need to convert it to strings\n                        (name.decode(\"latin-1\"), value.decode(\"latin-1\"))\n                        for name, value in message[\"headers\"]\n                    )\n                self.handshake_started_event.set()\n\n            elif message_type == \"websocket.close\":\n                message = cast(\"WebSocketCloseEvent\", message)\n                self.logger.info(\n                    '%s - \"WebSocket %s\" 403',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                self.initial_response = (http.HTTPStatus.FORBIDDEN, [], b\"\")\n                self.handshake_started_event.set()\n                self.closed_event.set()\n\n            elif message_type == \"websocket.http.response.start\":\n                message = cast(\"WebSocketResponseStartEvent\", message)\n                self.logger.info(\n                    '%s - \"WebSocket %s\" %d',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                    message[\"status\"],\n                )\n                # websockets requires the status to be an enum. look it up.\n                status = http.HTTPStatus(message[\"status\"])\n                headers = [\n                    (name.decode(\"latin-1\"), value.decode(\"latin-1\")) for name, value in message.get(\"headers\", [])\n                ]\n                self.initial_response = (status, headers, b\"\")\n                self.handshake_started_event.set()\n\n            else:\n                msg = (\n                    \"Expected ASGI message 'websocket.accept', 'websocket.close', \"\n                    \"or 'websocket.http.response.start' but got '%s'.\"\n                )\n                raise RuntimeError(msg % message_type)\n\n        elif not self.closed_event.is_set() and self.initial_response is None:\n            await self.handshake_completed_event.wait()\n\n            try:\n                if message_type == \"websocket.send\":\n                    message = cast(\"WebSocketSendEvent\", message)\n                    bytes_data = message.get(\"bytes\")\n                    text_data = message.get(\"text\")\n                    data = text_data if bytes_data is None else bytes_data\n                    await self.send(data)  # type: ignore[arg-type]\n\n                elif message_type == \"websocket.close\":\n                    message = cast(\"WebSocketCloseEvent\", message)\n                    code = message.get(\"code\", 1000)\n                    reason = message.get(\"reason\", \"\") or \"\"\n                    await self.close(code, reason)\n                    self.closed_event.set()\n\n                else:\n                    msg = \"Expected ASGI message 'websocket.send' or 'websocket.close', but got '%s'.\"\n                    raise RuntimeError(msg % message_type)\n            except ConnectionClosed as exc:\n                raise ClientDisconnected from exc\n\n        elif self.initial_response is not None:\n            if message_type == \"websocket.http.response.body\":\n                message = cast(\"WebSocketResponseBodyEvent\", message)\n                body = self.initial_response[2] + message[\"body\"]\n                self.initial_response = self.initial_response[:2] + (body,)\n                if not message.get(\"more_body\", False):\n                    self.closed_event.set()\n            else:\n                msg = \"Expected ASGI message 'websocket.http.response.body' but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n\n        else:\n            msg = \"Unexpected ASGI message '%s', after sending 'websocket.close' or response already completed.\"\n            raise RuntimeError(msg % message_type)\n\n    async def asgi_receive(self) -> WebSocketDisconnectEvent | WebSocketConnectEvent | WebSocketReceiveEvent:\n        if not self.connect_sent:\n            self.connect_sent = True\n            return {\"type\": \"websocket.connect\"}\n\n        await self.handshake_completed_event.wait()\n\n        if self.lost_connection_before_handshake:\n            # If the handshake failed or the app closed before handshake completion,\n            # use 1006 Abnormal Closure.\n            return {\"type\": \"websocket.disconnect\", \"code\": 1006}\n\n        if self.closed_event.is_set():\n            return {\"type\": \"websocket.disconnect\", \"code\": 1005}\n\n        try:\n            data = await self.recv()\n        except ConnectionClosed:\n            self.closed_event.set()\n            if self.ws_server.closing:\n                return {\"type\": \"websocket.disconnect\", \"code\": 1012}\n            return {\"type\": \"websocket.disconnect\", \"code\": self.close_code or 1005, \"reason\": self.close_reason}\n\n        if isinstance(data, str):\n            return {\"type\": \"websocket.receive\", \"text\": data}\n        return {\"type\": \"websocket.receive\", \"bytes\": data}\n"
  },
  {
    "path": "uvicorn/protocols/websockets/websockets_sansio_impl.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport sys\nfrom asyncio.transports import BaseTransport, Transport\nfrom http import HTTPStatus\nfrom typing import Any, Literal, cast\nfrom urllib.parse import unquote\n\nfrom websockets.exceptions import InvalidState\nfrom websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory\nfrom websockets.frames import Frame, Opcode\nfrom websockets.http11 import Request\nfrom websockets.server import ServerProtocol\n\nfrom uvicorn._types import (\n    ASGIReceiveEvent,\n    ASGISendEvent,\n    WebSocketAcceptEvent,\n    WebSocketCloseEvent,\n    WebSocketResponseBodyEvent,\n    WebSocketResponseStartEvent,\n    WebSocketScope,\n    WebSocketSendEvent,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.protocols.utils import (\n    ClientDisconnected,\n    get_client_addr,\n    get_local_addr,\n    get_path_with_query_string,\n    get_remote_addr,\n    is_ssl,\n)\nfrom uvicorn.server import ServerState\n\nif sys.version_info >= (3, 11):  # pragma: no cover\n    from typing import assert_never\nelse:  # pragma: no cover\n    from typing_extensions import assert_never\n\n\nclass WebSocketsSansIOProtocol(asyncio.Protocol):\n    def __init__(\n        self,\n        config: Config,\n        server_state: ServerState,\n        app_state: dict[str, Any],\n        _loop: asyncio.AbstractEventLoop | None = None,\n    ) -> None:\n        if not config.loaded:\n            config.load()  # pragma: no cover\n\n        self.config = config\n        self.app = config.loaded_app\n        self.loop = _loop or asyncio.get_event_loop()\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.root_path = config.root_path\n        self.app_state = app_state\n\n        # Shared server state\n        self.connections = server_state.connections\n        self.tasks = server_state.tasks\n        self.default_headers = server_state.default_headers\n\n        # Connection state\n        self.transport: asyncio.Transport = None  # type: ignore[assignment]\n        self.server: tuple[str, int | None] | None = None\n        self.client: tuple[str, int] | None = None\n        self.scheme: Literal[\"wss\", \"ws\"] = None  # type: ignore[assignment]\n\n        # WebSocket state\n        self.queue: asyncio.Queue[ASGIReceiveEvent] = asyncio.Queue()\n        self.handshake_initiated = False\n        self.handshake_complete = False\n        self.close_sent = False\n        self.initial_response: tuple[int, list[tuple[str, str]], bytes] | None = None\n\n        extensions = []\n        if self.config.ws_per_message_deflate:\n            extensions = [\n                ServerPerMessageDeflateFactory(\n                    server_max_window_bits=12,\n                    client_max_window_bits=12,\n                    compress_settings={\"memLevel\": 5},\n                )\n            ]\n        self.conn = ServerProtocol(\n            extensions=extensions,\n            max_size=self.config.ws_max_size,\n            logger=logging.getLogger(\"uvicorn.error\"),\n        )\n\n        self.read_paused = False\n        self.writable = asyncio.Event()\n        self.writable.set()\n\n        # Buffers\n        self.bytes = b\"\"\n\n    def connection_made(self, transport: BaseTransport) -> None:\n        \"\"\"Called when a connection is made.\"\"\"\n        transport = cast(Transport, transport)\n        self.connections.add(self)\n        self.transport = transport\n        self.server = get_local_addr(transport)\n        self.client = get_remote_addr(transport)\n        self.scheme = \"wss\" if is_ssl(transport) else \"ws\"\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection made\", prefix)\n\n    def connection_lost(self, exc: Exception | None) -> None:\n        code = 1005 if self.handshake_complete else 1006\n        self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code})\n        self.connections.remove(self)\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection lost\", prefix)\n\n        self.handshake_complete = True\n        if exc is None:\n            self.transport.close()\n\n    def eof_received(self) -> None:\n        pass\n\n    def shutdown(self) -> None:\n        if self.handshake_complete:\n            self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1012})\n            self.conn.send_close(1012)\n            output = self.conn.data_to_send()\n            self.transport.write(b\"\".join(output))\n        else:\n            self.send_500_response()\n        self.transport.close()\n\n    def data_received(self, data: bytes) -> None:\n        self.conn.receive_data(data)\n        if self.conn.parser_exc is not None:  # pragma: no cover\n            self.handle_parser_exception()\n            return\n        self.handle_events()\n\n    def handle_events(self) -> None:\n        for event in self.conn.events_received():\n            if isinstance(event, Request):\n                self.handle_connect(event)\n            if isinstance(event, Frame):\n                if event.opcode == Opcode.CONT:\n                    self.handle_cont(event)  # pragma: no cover\n                elif event.opcode == Opcode.TEXT:\n                    self.handle_text(event)\n                elif event.opcode == Opcode.BINARY:\n                    self.handle_bytes(event)\n                elif event.opcode == Opcode.PING:\n                    self.handle_ping()\n                elif event.opcode == Opcode.PONG:\n                    pass  # pragma: no cover\n                elif event.opcode == Opcode.CLOSE:\n                    self.handle_close(event)\n                else:\n                    assert_never(event.opcode)  # pragma: no cover\n\n    # Event handlers\n\n    def handle_connect(self, event: Request) -> None:\n        self.request = event\n        self.response = self.conn.accept(event)\n        self.handshake_initiated = True\n        if self.response.status_code != 101:\n            self.handshake_complete = True\n            self.close_sent = True\n            self.conn.send_response(self.response)\n            output = self.conn.data_to_send()\n            self.transport.write(b\"\".join(output))\n            self.transport.close()\n            return\n\n        headers = [\n            (key.encode(\"ascii\"), value.encode(\"ascii\", errors=\"surrogateescape\"))\n            for key, value in event.headers.raw_items()\n        ]\n        raw_path, _, query_string = event.path.partition(\"?\")\n        self.scope: WebSocketScope = {\n            \"type\": \"websocket\",\n            \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.4\"},\n            \"http_version\": \"1.1\",\n            \"scheme\": self.scheme,\n            \"server\": self.server,\n            \"client\": self.client,\n            \"root_path\": self.root_path,\n            \"path\": self.root_path + unquote(raw_path),\n            \"raw_path\": self.root_path.encode(\"ascii\") + raw_path.encode(\"ascii\"),\n            \"query_string\": query_string.encode(\"ascii\"),\n            \"headers\": headers,\n            \"subprotocols\": event.headers.get_all(\"Sec-WebSocket-Protocol\"),\n            \"state\": self.app_state.copy(),\n            \"extensions\": {\"websocket.http.response\": {}},\n        }\n        self.queue.put_nowait({\"type\": \"websocket.connect\"})\n        task = self.loop.create_task(self.run_asgi())\n        task.add_done_callback(self.on_task_complete)\n        self.tasks.add(task)\n\n    def handle_cont(self, event: Frame) -> None:  # pragma: no cover\n        self.bytes += event.data\n        if event.fin:\n            self.send_receive_event_to_app()\n\n    def handle_text(self, event: Frame) -> None:\n        self.bytes = event.data\n        self.curr_msg_data_type: Literal[\"text\", \"bytes\"] = \"text\"\n        if event.fin:\n            self.send_receive_event_to_app()\n\n    def handle_bytes(self, event: Frame) -> None:\n        self.bytes = event.data\n        self.curr_msg_data_type = \"bytes\"\n        if event.fin:\n            self.send_receive_event_to_app()\n\n    def send_receive_event_to_app(self) -> None:\n        if self.curr_msg_data_type == \"text\":\n            try:\n                self.queue.put_nowait({\"type\": \"websocket.receive\", \"text\": self.bytes.decode()})\n            except UnicodeDecodeError:  # pragma: no cover\n                self.logger.exception(\"Invalid UTF-8 sequence received from client.\")\n                self.conn.send_close(1007)\n                self.handle_parser_exception()\n                return\n        else:\n            self.queue.put_nowait({\"type\": \"websocket.receive\", \"bytes\": self.bytes})\n        if not self.read_paused:\n            self.read_paused = True\n            self.transport.pause_reading()\n\n    def handle_ping(self) -> None:\n        output = self.conn.data_to_send()\n        self.transport.write(b\"\".join(output))\n\n    def handle_close(self, event: Frame) -> None:\n        if not self.close_sent and not self.transport.is_closing():\n            assert self.conn.close_rcvd is not None\n            code = self.conn.close_rcvd.code\n            reason = self.conn.close_rcvd.reason\n            self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code, \"reason\": reason})\n\n            output = self.conn.data_to_send()\n            self.transport.write(b\"\".join(output))\n            self.transport.close()\n\n    def handle_parser_exception(self) -> None:  # pragma: no cover\n        assert self.conn.close_sent is not None\n        code = self.conn.close_sent.code\n        reason = self.conn.close_sent.reason\n        self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code, \"reason\": reason})\n\n        output = self.conn.data_to_send()\n        self.transport.write(b\"\".join(output))\n        self.close_sent = True\n        self.transport.close()\n\n    def on_task_complete(self, task: asyncio.Task[None]) -> None:\n        self.tasks.discard(task)\n\n    async def run_asgi(self) -> None:\n        try:\n            result = await self.app(self.scope, self.receive, self.send)\n        except ClientDisconnected:\n            pass  # pragma: full coverage\n        except BaseException:\n            self.logger.exception(\"Exception in ASGI application\\n\")\n            self.send_500_response()\n        else:\n            if not self.handshake_complete:\n                self.logger.error(\"ASGI callable returned without completing handshake.\")\n                self.send_500_response()\n            elif result is not None:\n                self.logger.error(\"ASGI callable should return None, but returned '%s'.\", result)\n        self.transport.close()\n\n    def send_500_response(self) -> None:\n        if self.initial_response or self.handshake_complete:\n            return\n        response = self.conn.reject(500, \"Internal Server Error\")\n        self.conn.send_response(response)\n        output = self.conn.data_to_send()\n        self.transport.write(b\"\".join(output))\n\n    async def send(self, message: ASGISendEvent) -> None:\n        await self.writable.wait()\n\n        message_type = message[\"type\"]\n\n        if not self.handshake_complete and self.initial_response is None:\n            if message_type == \"websocket.accept\":\n                message = cast(WebSocketAcceptEvent, message)\n                self.logger.info(\n                    '%s - \"WebSocket %s\" [accepted]',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                headers = [\n                    (name.decode(\"latin-1\").lower(), value.decode(\"latin-1\"))\n                    for name, value in (self.default_headers + list(message.get(\"headers\", [])))\n                ]\n                accepted_subprotocol = message.get(\"subprotocol\")\n                if accepted_subprotocol:\n                    headers.append((\"Sec-WebSocket-Protocol\", accepted_subprotocol))\n                self.response.headers.update(headers)\n\n                if not self.transport.is_closing():\n                    self.handshake_complete = True\n                    self.conn.send_response(self.response)\n                    output = self.conn.data_to_send()\n                    self.transport.write(b\"\".join(output))\n\n            elif message_type == \"websocket.close\":\n                message = cast(WebSocketCloseEvent, message)\n                self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1006})\n                self.logger.info(\n                    '%s - \"WebSocket %s\" 403',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                response = self.conn.reject(HTTPStatus.FORBIDDEN, \"\")\n                self.conn.send_response(response)\n                output = self.conn.data_to_send()\n                self.close_sent = True\n                self.handshake_complete = True\n                self.transport.write(b\"\".join(output))\n                self.transport.close()\n            elif message_type == \"websocket.http.response.start\" and self.initial_response is None:\n                message = cast(WebSocketResponseStartEvent, message)\n                if not (100 <= message[\"status\"] < 600):\n                    raise RuntimeError(\"Invalid HTTP status code '%d' in response.\" % message[\"status\"])\n                self.logger.info(\n                    '%s - \"WebSocket %s\" %d',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                    message[\"status\"],\n                )\n                headers = [\n                    (name.decode(\"latin-1\"), value.decode(\"latin-1\"))\n                    for name, value in list(message.get(\"headers\", []))\n                ]\n                self.initial_response = (message[\"status\"], headers, b\"\")\n            else:\n                msg = (\n                    \"Expected ASGI message 'websocket.accept', 'websocket.close' \"\n                    \"or 'websocket.http.response.start' \"\n                    \"but got '%s'.\"\n                )\n                raise RuntimeError(msg % message_type)\n\n        elif not self.close_sent and self.initial_response is None:\n            try:\n                if message_type == \"websocket.send\":\n                    message = cast(WebSocketSendEvent, message)\n                    bytes_data = message.get(\"bytes\")\n                    text_data = message.get(\"text\")\n                    if bytes_data is not None:\n                        self.conn.send_binary(bytes_data)\n                    elif text_data is not None:\n                        self.conn.send_text(text_data.encode())\n                    output = self.conn.data_to_send()\n                    self.transport.write(b\"\".join(output))\n\n                elif message_type == \"websocket.close\":\n                    if not self.transport.is_closing():\n                        message = cast(WebSocketCloseEvent, message)\n                        code = message.get(\"code\", 1000)\n                        reason = message.get(\"reason\", \"\") or \"\"\n                        self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code, \"reason\": reason})\n                        self.conn.send_close(code, reason)\n                        output = self.conn.data_to_send()\n                        self.transport.write(b\"\".join(output))\n                        self.close_sent = True\n                        self.transport.close()\n                else:\n                    msg = \"Expected ASGI message 'websocket.send' or 'websocket.close', but got '%s'.\"\n                    raise RuntimeError(msg % message_type)\n            except InvalidState:\n                raise ClientDisconnected()\n        elif self.initial_response is not None:\n            if message_type == \"websocket.http.response.body\":\n                message = cast(WebSocketResponseBodyEvent, message)\n                body = self.initial_response[2] + message[\"body\"]\n                self.initial_response = self.initial_response[:2] + (body,)\n                if not message.get(\"more_body\", False):\n                    response = self.conn.reject(self.initial_response[0], body.decode())\n                    response.headers.update(self.initial_response[1])\n                    self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1006})\n                    self.conn.send_response(response)\n                    output = self.conn.data_to_send()\n                    self.close_sent = True\n                    self.transport.write(b\"\".join(output))\n                    self.transport.close()\n            else:  # pragma: no cover\n                msg = \"Expected ASGI message 'websocket.http.response.body' but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n\n        else:\n            msg = \"Unexpected ASGI message '%s', after sending 'websocket.close'.\"\n            raise RuntimeError(msg % message_type)\n\n    async def receive(self) -> ASGIReceiveEvent:\n        message = await self.queue.get()\n        if self.read_paused and self.queue.empty():\n            self.read_paused = False\n            self.transport.resume_reading()\n        return message\n"
  },
  {
    "path": "uvicorn/protocols/websockets/wsproto_impl.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nfrom typing import Any, Literal, cast\nfrom urllib.parse import unquote\n\nimport wsproto\nfrom wsproto import ConnectionType, events\nfrom wsproto.connection import ConnectionState\nfrom wsproto.extensions import Extension, PerMessageDeflate\nfrom wsproto.utilities import LocalProtocolError, RemoteProtocolError\n\nfrom uvicorn._types import (\n    ASGI3Application,\n    ASGISendEvent,\n    WebSocketAcceptEvent,\n    WebSocketCloseEvent,\n    WebSocketEvent,\n    WebSocketResponseBodyEvent,\n    WebSocketResponseStartEvent,\n    WebSocketScope,\n    WebSocketSendEvent,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.logging import TRACE_LOG_LEVEL\nfrom uvicorn.protocols.utils import (\n    ClientDisconnected,\n    get_client_addr,\n    get_local_addr,\n    get_path_with_query_string,\n    get_remote_addr,\n    is_ssl,\n)\nfrom uvicorn.server import ServerState\n\n\nclass WSProtocol(asyncio.Protocol):\n    def __init__(\n        self,\n        config: Config,\n        server_state: ServerState,\n        app_state: dict[str, Any],\n        _loop: asyncio.AbstractEventLoop | None = None,\n    ) -> None:\n        if not config.loaded:\n            config.load()  # pragma: full coverage\n\n        self.config = config\n        self.app = cast(ASGI3Application, config.loaded_app)\n        self.loop = _loop or asyncio.get_event_loop()\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.root_path = config.root_path\n        self.app_state = app_state\n\n        # Shared server state\n        self.connections = server_state.connections\n        self.tasks = server_state.tasks\n        self.default_headers = server_state.default_headers\n\n        # Connection state\n        self.transport: asyncio.Transport = None  # type: ignore[assignment]\n        self.server: tuple[str, int | None] | None = None\n        self.client: tuple[str, int] | None = None\n        self.scheme: Literal[\"wss\", \"ws\"] = None  # type: ignore[assignment]\n\n        # WebSocket state\n        self.queue: asyncio.Queue[WebSocketEvent] = asyncio.Queue()\n        self.handshake_complete = False\n        self.close_sent = False\n\n        # Rejection state\n        self.response_started = False\n\n        self.conn = wsproto.WSConnection(connection_type=ConnectionType.SERVER)\n\n        self.read_paused = False\n        self.writable = asyncio.Event()\n        self.writable.set()\n\n        # Buffers\n        self.bytes = b\"\"\n        self.text = \"\"\n\n    # Protocol interface\n\n    def connection_made(  # type: ignore[override]\n        self, transport: asyncio.Transport\n    ) -> None:\n        self.connections.add(self)\n        self.transport = transport\n        self.server = get_local_addr(transport)\n        self.client = get_remote_addr(transport)\n        self.scheme = \"wss\" if is_ssl(transport) else \"ws\"\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection made\", prefix)\n\n    def connection_lost(self, exc: Exception | None) -> None:\n        code = 1005 if self.handshake_complete else 1006\n        self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code})\n        self.connections.remove(self)\n\n        if self.logger.level <= TRACE_LOG_LEVEL:\n            prefix = \"%s:%d - \" % self.client if self.client else \"\"\n            self.logger.log(TRACE_LOG_LEVEL, \"%sWebSocket connection lost\", prefix)\n\n        self.handshake_complete = True\n        if exc is None:\n            self.transport.close()\n\n    def eof_received(self) -> None:\n        pass\n\n    def data_received(self, data: bytes) -> None:\n        try:\n            self.conn.receive_data(data)\n        except RemoteProtocolError as err:\n            # TODO: Remove `type: ignore` when wsproto fixes the type annotation.\n            self.transport.write(self.conn.send(err.event_hint))  # type: ignore[arg-type]  # noqa: E501\n            self.transport.close()\n        else:\n            self.handle_events()\n\n    def handle_events(self) -> None:\n        for event in self.conn.events():\n            if isinstance(event, events.Request):\n                self.handle_connect(event)\n            elif isinstance(event, events.TextMessage):\n                self.handle_text(event)\n            elif isinstance(event, events.BytesMessage):\n                self.handle_bytes(event)\n            elif isinstance(event, events.CloseConnection):\n                self.handle_close(event)\n            elif isinstance(event, events.Ping):\n                self.handle_ping(event)\n\n    def pause_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer exceeds the high water mark.\n        \"\"\"\n        self.writable.clear()  # pragma: full coverage\n\n    def resume_writing(self) -> None:\n        \"\"\"\n        Called by the transport when the write buffer drops below the low water mark.\n        \"\"\"\n        self.writable.set()  # pragma: full coverage\n\n    def shutdown(self) -> None:\n        if self.handshake_complete:\n            self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1012})\n            output = self.conn.send(wsproto.events.CloseConnection(code=1012))\n            self.transport.write(output)\n        else:\n            self.send_500_response()\n        self.transport.close()\n\n    def on_task_complete(self, task: asyncio.Task[None]) -> None:\n        self.tasks.discard(task)\n\n    # Event handlers\n\n    def handle_connect(self, event: events.Request) -> None:\n        headers = [(b\"host\", event.host.encode())]\n        headers += [(key.lower(), value) for key, value in event.extra_headers]\n        raw_path, _, query_string = event.target.partition(\"?\")\n        path = unquote(raw_path)\n        full_path = self.root_path + path\n        full_raw_path = self.root_path.encode(\"ascii\") + raw_path.encode(\"ascii\")\n        self.scope: WebSocketScope = {\n            \"type\": \"websocket\",\n            \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.4\"},\n            \"http_version\": \"1.1\",\n            \"scheme\": self.scheme,\n            \"server\": self.server,\n            \"client\": self.client,\n            \"root_path\": self.root_path,\n            \"path\": full_path,\n            \"raw_path\": full_raw_path,\n            \"query_string\": query_string.encode(\"ascii\"),\n            \"headers\": headers,\n            \"subprotocols\": event.subprotocols,\n            \"state\": self.app_state.copy(),\n            \"extensions\": {\"websocket.http.response\": {}},\n        }\n        self.queue.put_nowait({\"type\": \"websocket.connect\"})\n        task = self.loop.create_task(self.run_asgi())\n        task.add_done_callback(self.on_task_complete)\n        self.tasks.add(task)\n\n    def handle_text(self, event: events.TextMessage) -> None:\n        self.text += event.data\n        if event.message_finished:\n            self.queue.put_nowait({\"type\": \"websocket.receive\", \"text\": self.text})\n            self.text = \"\"\n            if not self.read_paused:\n                self.read_paused = True\n                self.transport.pause_reading()\n\n    def handle_bytes(self, event: events.BytesMessage) -> None:\n        self.bytes += event.data\n        # todo: we may want to guard the size of self.bytes and self.text\n        if event.message_finished:\n            self.queue.put_nowait({\"type\": \"websocket.receive\", \"bytes\": self.bytes})\n            self.bytes = b\"\"\n            if not self.read_paused:\n                self.read_paused = True\n                self.transport.pause_reading()\n\n    def handle_close(self, event: events.CloseConnection) -> None:\n        if self.conn.state == ConnectionState.REMOTE_CLOSING:\n            self.transport.write(self.conn.send(event.response()))\n        self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": event.code, \"reason\": event.reason})\n        self.transport.close()\n\n    def handle_ping(self, event: events.Ping) -> None:\n        self.transport.write(self.conn.send(event.response()))\n\n    def send_500_response(self) -> None:\n        if self.response_started or self.handshake_complete:\n            return  # we cannot send responses anymore\n        headers: list[tuple[bytes, bytes]] = [\n            (b\"content-type\", b\"text/plain; charset=utf-8\"),\n            (b\"connection\", b\"close\"),\n            (b\"content-length\", b\"21\"),\n        ]\n        output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True))\n        output += self.conn.send(wsproto.events.RejectData(data=b\"Internal Server Error\"))\n        self.transport.write(output)\n\n    async def run_asgi(self) -> None:\n        try:\n            result = await self.app(self.scope, self.receive, self.send)  # type: ignore[func-returns-value]\n        except ClientDisconnected:\n            pass  # pragma: full coverage\n        except BaseException:\n            self.logger.exception(\"Exception in ASGI application\\n\")\n            self.send_500_response()\n        else:\n            if not self.handshake_complete:\n                self.logger.error(\"ASGI callable returned without completing handshake.\")\n                self.send_500_response()\n            elif result is not None:\n                self.logger.error(\"ASGI callable should return None, but returned '%s'.\", result)\n        self.transport.close()\n\n    async def send(self, message: ASGISendEvent) -> None:\n        await self.writable.wait()\n\n        message_type = message[\"type\"]\n\n        if not self.handshake_complete:\n            if message_type == \"websocket.accept\":\n                message = cast(WebSocketAcceptEvent, message)\n                self.logger.info(\n                    '%s - \"WebSocket %s\" [accepted]',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                subprotocol = message.get(\"subprotocol\")\n                extra_headers = self.default_headers + list(message.get(\"headers\", []))\n                extensions: list[Extension] = []\n                if self.config.ws_per_message_deflate:\n                    extensions.append(PerMessageDeflate())\n                if not self.transport.is_closing():\n                    self.handshake_complete = True\n                    output = self.conn.send(\n                        wsproto.events.AcceptConnection(\n                            subprotocol=subprotocol,\n                            extensions=extensions,\n                            extra_headers=extra_headers,\n                        )\n                    )\n                    self.transport.write(output)\n\n            elif message_type == \"websocket.close\":\n                self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1006})\n                self.logger.info(\n                    '%s - \"WebSocket %s\" 403',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                )\n                self.handshake_complete = True\n                self.close_sent = True\n                event = events.RejectConnection(status_code=403, headers=[])\n                output = self.conn.send(event)\n                self.transport.write(output)\n                self.transport.close()\n\n            elif message_type == \"websocket.http.response.start\":\n                message = cast(WebSocketResponseStartEvent, message)\n                # ensure status code is in the valid range\n                if not (100 <= message[\"status\"] < 600):\n                    msg = \"Invalid HTTP status code '%d' in response.\"\n                    raise RuntimeError(msg % message[\"status\"])\n                self.logger.info(\n                    '%s - \"WebSocket %s\" %d',\n                    get_client_addr(self.scope),\n                    get_path_with_query_string(self.scope),\n                    message[\"status\"],\n                )\n                self.handshake_complete = True\n                event = events.RejectConnection(\n                    status_code=message[\"status\"],\n                    headers=list(message[\"headers\"]),\n                    has_body=True,\n                )\n                output = self.conn.send(event)\n                self.transport.write(output)\n                self.response_started = True\n\n            else:\n                msg = (\n                    \"Expected ASGI message 'websocket.accept', 'websocket.close' \"\n                    \"or 'websocket.http.response.start' \"\n                    \"but got '%s'.\"\n                )\n                raise RuntimeError(msg % message_type)\n\n        elif not self.close_sent and not self.response_started:\n            try:\n                if message_type == \"websocket.send\":\n                    message = cast(WebSocketSendEvent, message)\n                    bytes_data = message.get(\"bytes\")\n                    text_data = message.get(\"text\")\n                    data = text_data if bytes_data is None else bytes_data\n                    output = self.conn.send(wsproto.events.Message(data=data))  # type: ignore\n                    if not self.transport.is_closing():\n                        self.transport.write(output)\n\n                elif message_type == \"websocket.close\":\n                    message = cast(WebSocketCloseEvent, message)\n                    self.close_sent = True\n                    code = message.get(\"code\", 1000)\n                    reason = message.get(\"reason\", \"\") or \"\"\n                    self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": code, \"reason\": reason})\n                    output = self.conn.send(wsproto.events.CloseConnection(code=code, reason=reason))\n                    if not self.transport.is_closing():\n                        self.transport.write(output)\n                        self.transport.close()\n\n                else:\n                    msg = \"Expected ASGI message 'websocket.send' or 'websocket.close', but got '%s'.\"\n                    raise RuntimeError(msg % message_type)\n            except LocalProtocolError as exc:\n                raise ClientDisconnected from exc\n        elif self.response_started:\n            if message_type == \"websocket.http.response.body\":\n                message = cast(\"WebSocketResponseBodyEvent\", message)\n                body_finished = not message.get(\"more_body\", False)\n                reject_data = events.RejectData(data=message[\"body\"], body_finished=body_finished)\n                output = self.conn.send(reject_data)\n                self.transport.write(output)\n\n                if body_finished:\n                    self.queue.put_nowait({\"type\": \"websocket.disconnect\", \"code\": 1006})\n                    self.close_sent = True\n                    self.transport.close()\n\n            else:\n                msg = \"Expected ASGI message 'websocket.http.response.body' but got '%s'.\"\n                raise RuntimeError(msg % message_type)\n\n        else:\n            msg = \"Unexpected ASGI message '%s', after sending 'websocket.close'.\"\n            raise RuntimeError(msg % message_type)\n\n    async def receive(self) -> WebSocketEvent:\n        message = await self.queue.get()\n        if self.read_paused and self.queue.empty():\n            self.read_paused = False\n            self.transport.resume_reading()\n        return message\n"
  },
  {
    "path": "uvicorn/py.typed",
    "content": "\n"
  },
  {
    "path": "uvicorn/server.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport contextlib\nimport functools\nimport logging\nimport os\nimport platform\nimport random\nimport signal\nimport socket\nimport sys\nimport threading\nimport time\nfrom collections.abc import Generator, Sequence\nfrom email.utils import formatdate\nfrom types import FrameType\nfrom typing import TYPE_CHECKING, TypeAlias\n\nimport click\n\nfrom uvicorn._compat import asyncio_run\nfrom uvicorn.config import Config\n\nif TYPE_CHECKING:\n    from uvicorn.protocols.http.h11_impl import H11Protocol\n    from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol\n    from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol\n    from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol\n    from uvicorn.protocols.websockets.wsproto_impl import WSProtocol\n\n    Protocols: TypeAlias = H11Protocol | HttpToolsProtocol | WSProtocol | WebSocketProtocol | WebSocketsSansIOProtocol\n\nHANDLED_SIGNALS = (\n    signal.SIGINT,  # Unix signal 2. Sent by Ctrl+C.\n    signal.SIGTERM,  # Unix signal 15. Sent by `kill <pid>`.\n)\nif sys.platform == \"win32\":  # pragma: py-not-win32\n    HANDLED_SIGNALS += (signal.SIGBREAK,)  # Windows signal 21. Sent by Ctrl+Break.\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\nclass ServerState:\n    \"\"\"\n    Shared servers state that is available between all protocol instances.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.total_requests = 0\n        self.connections: set[Protocols] = set()\n        self.tasks: set[asyncio.Task[None]] = set()\n        self.default_headers: list[tuple[bytes, bytes]] = []\n\n\nclass Server:\n    def __init__(self, config: Config) -> None:\n        self.config = config\n        self.server_state = ServerState()\n\n        self.started = False\n        self.should_exit = False\n        self.force_exit = False\n        self.last_notified = 0.0\n\n        self._captured_signals: list[int] = []\n\n    @functools.cached_property\n    def limit_max_requests(self) -> int | None:\n        if self.config.limit_max_requests is None:\n            return None\n        return self.config.limit_max_requests + random.randint(0, self.config.limit_max_requests_jitter)\n\n    def run(self, sockets: list[socket.socket] | None = None) -> None:\n        return asyncio_run(self.serve(sockets=sockets), loop_factory=self.config.get_loop_factory())\n\n    async def serve(self, sockets: list[socket.socket] | None = None) -> None:\n        with self.capture_signals():\n            await self._serve(sockets)\n\n    async def _serve(self, sockets: list[socket.socket] | None = None) -> None:\n        process_id = os.getpid()\n\n        config = self.config\n        if not config.loaded:\n            config.load()\n\n        self.lifespan = config.lifespan_class(config)\n\n        message = \"Started server process [%d]\"\n        color_message = \"Started server process [\" + click.style(\"%d\", fg=\"cyan\") + \"]\"\n        logger.info(message, process_id, extra={\"color_message\": color_message})\n\n        await self.startup(sockets=sockets)\n        if not self.should_exit:\n            await self.main_loop()\n        if self.started:\n            await self.shutdown(sockets=sockets)\n\n            message = \"Finished server process [%d]\"\n            color_message = \"Finished server process [\" + click.style(\"%d\", fg=\"cyan\") + \"]\"\n            logger.info(message, process_id, extra={\"color_message\": color_message})\n\n    async def startup(self, sockets: list[socket.socket] | None = None) -> None:\n        await self.lifespan.startup()\n        if self.lifespan.should_exit:\n            self.should_exit = True\n            return\n\n        config = self.config\n\n        def create_protocol(\n            _loop: asyncio.AbstractEventLoop | None = None,\n        ) -> asyncio.Protocol:\n            return config.http_protocol_class(  # type: ignore[call-arg]\n                config=config,\n                server_state=self.server_state,\n                app_state=self.lifespan.state,\n                _loop=_loop,\n            )\n\n        loop = asyncio.get_running_loop()\n\n        listeners: Sequence[socket.SocketType]\n        if sockets is not None:  # pragma: full coverage\n            # Explicitly passed a list of open sockets.\n            # We use this when the server is run from a Gunicorn worker.\n\n            def _share_socket(\n                sock: socket.SocketType,\n            ) -> socket.SocketType:  # pragma py-not-win32\n                # Windows requires the socket be explicitly shared across\n                # multiple workers (processes).\n                from socket import fromshare  # type: ignore[attr-defined]\n\n                sock_data = sock.share(os.getpid())  # type: ignore[attr-defined]\n                return fromshare(sock_data)\n\n            self.servers: list[asyncio.base_events.Server] = []\n            for sock in sockets:\n                is_windows = platform.system() == \"Windows\"\n                if config.workers > 1 and is_windows:  # pragma: py-not-win32\n                    sock = _share_socket(sock)  # type: ignore[assignment]\n                server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)\n                self.servers.append(server)\n            listeners = sockets\n\n        elif config.fd is not None:  # pragma: py-win32\n            # Use an existing socket, from a file descriptor.\n            sock = socket.fromfd(config.fd, socket.AF_UNIX, socket.SOCK_STREAM)\n            server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)\n            assert server.sockets is not None  # mypy\n            listeners = server.sockets\n            self.servers = [server]\n\n        elif config.uds is not None:  # pragma: py-win32\n            # Create a socket using UNIX domain socket.\n            uds_perms = 0o666\n            if os.path.exists(config.uds):\n                uds_perms = os.stat(config.uds).st_mode  # pragma: full coverage\n            server = await loop.create_unix_server(\n                create_protocol, path=config.uds, ssl=config.ssl, backlog=config.backlog\n            )\n            os.chmod(config.uds, uds_perms)\n            assert server.sockets is not None  # mypy\n            listeners = server.sockets\n            self.servers = [server]\n\n        else:\n            # Standard case. Create a socket from a host/port pair.\n            try:\n                server = await loop.create_server(\n                    create_protocol,\n                    host=config.host,\n                    port=config.port,\n                    ssl=config.ssl,\n                    backlog=config.backlog,\n                )\n            except OSError as exc:\n                logger.error(exc)\n                await self.lifespan.shutdown()\n                sys.exit(1)\n\n            assert server.sockets is not None\n            listeners = server.sockets\n            self.servers = [server]\n\n        if sockets is None:\n            self._log_started_message(listeners)\n        else:\n            # We're most likely running multiple workers, so a message has already been\n            # logged by `config.bind_socket()`.\n            pass  # pragma: full coverage\n\n        self.started = True\n\n    def _log_started_message(self, listeners: Sequence[socket.SocketType]) -> None:\n        config = self.config\n\n        if config.fd is not None:  # pragma: py-win32\n            sock = listeners[0]\n            logger.info(\n                \"Uvicorn running on socket %s (Press CTRL+C to quit)\",\n                sock.getsockname(),\n            )\n\n        elif config.uds is not None:  # pragma: py-win32\n            logger.info(\"Uvicorn running on unix socket %s (Press CTRL+C to quit)\", config.uds)\n\n        else:\n            addr_format = \"%s://%s:%d\"\n            host = \"0.0.0.0\" if config.host is None else config.host\n            if \":\" in host:\n                # It's an IPv6 address.\n                addr_format = \"%s://[%s]:%d\"\n\n            port = config.port\n            if port == 0:\n                port = listeners[0].getsockname()[1]\n\n            protocol_name = \"https\" if config.ssl else \"http\"\n            message = f\"Uvicorn running on {addr_format} (Press CTRL+C to quit)\"\n            color_message = \"Uvicorn running on \" + click.style(addr_format, bold=True) + \" (Press CTRL+C to quit)\"\n            logger.info(\n                message,\n                protocol_name,\n                host,\n                port,\n                extra={\"color_message\": color_message},\n            )\n\n    async def main_loop(self) -> None:\n        counter = 0\n        should_exit = await self.on_tick(counter)\n        while not should_exit:\n            counter += 1\n            counter = counter % 864000\n            await asyncio.sleep(0.1)\n            should_exit = await self.on_tick(counter)\n\n    async def on_tick(self, counter: int) -> bool:\n        # Update the default headers, once per second.\n        if counter % 10 == 0:\n            current_time = time.time()\n            current_date = formatdate(current_time, usegmt=True).encode()\n\n            if self.config.date_header:\n                date_header = [(b\"date\", current_date)]\n            else:\n                date_header = []\n\n            self.server_state.default_headers = date_header + self.config.encoded_headers\n\n            # Callback to `callback_notify` once every `timeout_notify` seconds.\n            if self.config.callback_notify is not None:\n                if current_time - self.last_notified > self.config.timeout_notify:  # pragma: full coverage\n                    self.last_notified = current_time\n                    await self.config.callback_notify()\n\n        # Determine if we should exit.\n        if self.should_exit:\n            return True\n\n        max_requests = self.limit_max_requests\n        if max_requests is not None and self.server_state.total_requests >= max_requests:\n            logger.info(\"Maximum request limit of %d exceeded. Terminating process.\", max_requests)\n            return True\n\n        return False\n\n    async def shutdown(self, sockets: list[socket.socket] | None = None) -> None:\n        logger.info(\"Shutting down\")\n\n        # Stop accepting new connections.\n        for server in self.servers:\n            server.close()\n        for sock in sockets or []:\n            sock.close()  # pragma: full coverage\n\n        # Request shutdown on all existing connections.\n        for connection in list(self.server_state.connections):\n            connection.shutdown()\n        await asyncio.sleep(0.1)\n\n        # When 3.10 is not supported anymore, use `async with asyncio.timeout(...):`.\n        try:\n            await asyncio.wait_for(\n                self._wait_tasks_to_complete(),\n                timeout=self.config.timeout_graceful_shutdown,\n            )\n        except asyncio.TimeoutError:\n            logger.error(\n                \"Cancel %s running task(s), timeout graceful shutdown exceeded\",\n                len(self.server_state.tasks),\n            )\n            for t in self.server_state.tasks:\n                t.cancel(msg=\"Task cancelled, timeout graceful shutdown exceeded\")\n\n        # Send the lifespan shutdown event, and wait for application shutdown.\n        if not self.force_exit:\n            await self.lifespan.shutdown()\n\n    async def _wait_tasks_to_complete(self) -> None:\n        # Wait for existing connections to finish sending responses.\n        if self.server_state.connections and not self.force_exit:\n            msg = \"Waiting for connections to close. (CTRL+C to force quit)\"\n            logger.info(msg)\n            while self.server_state.connections and not self.force_exit:\n                await asyncio.sleep(0.1)\n\n        # Wait for existing tasks to complete.\n        if self.server_state.tasks and not self.force_exit:\n            msg = \"Waiting for background tasks to complete. (CTRL+C to force quit)\"\n            logger.info(msg)\n            while self.server_state.tasks and not self.force_exit:\n                await asyncio.sleep(0.1)\n\n        for server in self.servers:\n            await server.wait_closed()\n\n    @contextlib.contextmanager\n    def capture_signals(self) -> Generator[None, None, None]:\n        # Signals can only be listened to from the main thread.\n        if threading.current_thread() is not threading.main_thread():\n            yield\n            return\n        # always use signal.signal, even if loop.add_signal_handler is available\n        # this allows to restore previous signal handlers later on\n        original_handlers = {sig: signal.signal(sig, self.handle_exit) for sig in HANDLED_SIGNALS}\n        try:\n            yield\n        finally:\n            for sig, handler in original_handlers.items():\n                signal.signal(sig, handler)\n        # If we did gracefully shut down due to a signal, try to\n        # trigger the expected behaviour now; multiple signals would be\n        # done LIFO, see https://stackoverflow.com/questions/48434964\n        for captured_signal in reversed(self._captured_signals):\n            signal.raise_signal(captured_signal)\n\n    def handle_exit(self, sig: int, frame: FrameType | None) -> None:\n        self._captured_signals.append(sig)\n        if self.should_exit and sig == signal.SIGINT:\n            self.force_exit = True  # pragma: full coverage\n        else:\n            self.should_exit = True\n"
  },
  {
    "path": "uvicorn/supervisors/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom uvicorn.supervisors.basereload import BaseReload\nfrom uvicorn.supervisors.multiprocess import Multiprocess\n\nif TYPE_CHECKING:\n    ChangeReload: type[BaseReload]\nelse:\n    try:\n        from uvicorn.supervisors.watchfilesreload import WatchFilesReload as ChangeReload\n    except ImportError:  # pragma: no cover\n        from uvicorn.supervisors.statreload import StatReload as ChangeReload\n\n__all__ = [\"Multiprocess\", \"ChangeReload\"]\n"
  },
  {
    "path": "uvicorn/supervisors/basereload.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport os\nimport signal\nimport sys\nimport threading\nfrom collections.abc import Callable, Iterator\nfrom pathlib import Path\nfrom socket import socket\nfrom types import FrameType\n\nimport click\n\nfrom uvicorn._subprocess import get_subprocess\nfrom uvicorn.config import Config\n\nHANDLED_SIGNALS = (\n    signal.SIGINT,  # Unix signal 2. Sent by Ctrl+C.\n    signal.SIGTERM,  # Unix signal 15. Sent by `kill <pid>`.\n)\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\nclass BaseReload:\n    def __init__(\n        self,\n        config: Config,\n        target: Callable[[list[socket] | None], None],\n        sockets: list[socket],\n    ) -> None:\n        self.config = config\n        self.target = target\n        self.sockets = sockets\n        self.should_exit = threading.Event()\n        self.pid = os.getpid()\n        self.is_restarting = False\n        self.reloader_name: str | None = None\n\n    def signal_handler(self, sig: int, frame: FrameType | None) -> None:  # pragma: full coverage\n        \"\"\"\n        A signal handler that is registered with the parent process.\n        \"\"\"\n        if sys.platform == \"win32\" and self.is_restarting:\n            self.is_restarting = False\n        else:\n            self.should_exit.set()\n\n    def run(self) -> None:\n        self.startup()\n        for changes in self:\n            if changes:\n                logger.warning(\n                    \"%s detected changes in %s. Reloading...\",\n                    self.reloader_name,\n                    \", \".join(map(_display_path, changes)),\n                )\n                self.restart()\n\n        self.shutdown()\n\n    def pause(self) -> None:\n        if self.should_exit.wait(self.config.reload_delay):\n            raise StopIteration()\n\n    def __iter__(self) -> Iterator[list[Path] | None]:\n        return self\n\n    def __next__(self) -> list[Path] | None:\n        return self.should_restart()\n\n    def startup(self) -> None:\n        message = f\"Started reloader process [{self.pid}] using {self.reloader_name}\"\n        color_message = \"Started reloader process [{}] using {}\".format(\n            click.style(str(self.pid), fg=\"cyan\", bold=True),\n            click.style(str(self.reloader_name), fg=\"cyan\", bold=True),\n        )\n        logger.info(message, extra={\"color_message\": color_message})\n\n        for sig in HANDLED_SIGNALS:\n            signal.signal(sig, self.signal_handler)\n\n        self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets)\n        self.process.start()\n\n    def restart(self) -> None:\n        if sys.platform == \"win32\":  # pragma: py-not-win32\n            self.is_restarting = True\n            assert self.process.pid is not None\n            os.kill(self.process.pid, signal.CTRL_C_EVENT)\n\n            # This is a workaround to ensure the Ctrl+C event is processed\n            sys.stdout.write(\" \")  # This has to be a non-empty string\n            sys.stdout.flush()\n        else:  # pragma: py-win32\n            self.process.terminate()\n        self.process.join()\n\n        self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets)\n        self.process.start()\n\n    def shutdown(self) -> None:\n        if sys.platform == \"win32\":\n            self.should_exit.set()  # pragma: py-not-win32\n        else:\n            self.process.terminate()  # pragma: py-win32\n        self.process.join()\n\n        for sock in self.sockets:\n            sock.close()\n\n        message = f\"Stopping reloader process [{str(self.pid)}]\"\n        color_message = \"Stopping reloader process [{}]\".format(click.style(str(self.pid), fg=\"cyan\", bold=True))\n        logger.info(message, extra={\"color_message\": color_message})\n\n    def should_restart(self) -> list[Path] | None:\n        raise NotImplementedError(\"Reload strategies should override should_restart()\")\n\n\ndef _display_path(path: Path) -> str:\n    try:\n        return f\"'{path.relative_to(Path.cwd())}'\"\n    except ValueError:\n        return f\"'{path}'\"\n"
  },
  {
    "path": "uvicorn/supervisors/multiprocess.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport os\nimport signal\nimport threading\nfrom collections.abc import Callable\nfrom multiprocessing import Pipe\nfrom socket import socket\nfrom typing import Any\n\nimport click\n\nfrom uvicorn._subprocess import get_subprocess\nfrom uvicorn.config import Config\n\nSIGNALS = {\n    getattr(signal, f\"SIG{x}\"): x\n    for x in \"INT TERM BREAK HUP QUIT TTIN TTOU USR1 USR2 WINCH\".split()\n    if hasattr(signal, f\"SIG{x}\")\n}\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\nclass Process:\n    def __init__(\n        self,\n        config: Config,\n        target: Callable[[list[socket] | None], None],\n        sockets: list[socket],\n    ) -> None:\n        self.real_target = target\n\n        self.parent_conn, self.child_conn = Pipe()\n        self.process = get_subprocess(config, self.target, sockets)\n\n    def ping(self, timeout: float = 5) -> bool:\n        self.parent_conn.send(b\"ping\")\n        if self.parent_conn.poll(timeout):\n            self.parent_conn.recv()\n            return True\n        return False\n\n    def pong(self) -> None:\n        self.child_conn.recv()\n        self.child_conn.send(b\"pong\")\n\n    def always_pong(self) -> None:\n        while True:\n            self.pong()\n\n    def target(self, sockets: list[socket] | None = None) -> Any:  # pragma: no cover\n        if os.name == \"nt\":  # pragma: py-not-win32\n            # Windows doesn't support SIGTERM, so we use SIGBREAK instead.\n            # And then we raise SIGTERM when SIGBREAK is received.\n            # https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/signal?view=msvc-170\n            signal.signal(\n                signal.SIGBREAK,  # type: ignore[attr-defined]\n                lambda sig, frame: signal.raise_signal(signal.SIGTERM),\n            )\n\n        threading.Thread(target=self.always_pong, daemon=True).start()\n        return self.real_target(sockets)\n\n    def is_alive(self, timeout: float = 5) -> bool:\n        if not self.process.is_alive():\n            return False  # pragma: full coverage\n\n        return self.ping(timeout)\n\n    def start(self) -> None:\n        self.process.start()\n\n    def terminate(self) -> None:\n        if self.process.exitcode is None:  # Process is still running\n            assert self.process.pid is not None\n            if os.name == \"nt\":  # pragma: py-not-win32\n                # Windows doesn't support SIGTERM.\n                # So send SIGBREAK, and then in process raise SIGTERM.\n                os.kill(self.process.pid, signal.CTRL_BREAK_EVENT)  # type: ignore[attr-defined]\n            else:\n                os.kill(self.process.pid, signal.SIGTERM)\n            logger.info(f\"Terminated child process [{self.process.pid}]\")\n\n            self.parent_conn.close()\n            self.child_conn.close()\n\n    def kill(self) -> None:\n        # In Windows, the method will call `TerminateProcess` to kill the process.\n        # In Unix, the method will send SIGKILL to the process.\n        self.process.kill()\n\n    def join(self) -> None:\n        logger.info(f\"Waiting for child process [{self.process.pid}]\")\n        self.process.join()\n\n    @property\n    def pid(self) -> int | None:\n        return self.process.pid\n\n\nclass Multiprocess:\n    def __init__(\n        self,\n        config: Config,\n        target: Callable[[list[socket] | None], None],\n        sockets: list[socket],\n    ) -> None:\n        self.config = config\n        self.target = target\n        self.sockets = sockets\n\n        self.processes_num = config.workers\n        self.processes: list[Process] = []\n\n        self.should_exit = threading.Event()\n\n        self.signal_queue: list[int] = []\n        for sig in SIGNALS:\n            signal.signal(sig, lambda sig, frame: self.signal_queue.append(sig))\n\n    def init_processes(self) -> None:\n        for _ in range(self.processes_num):\n            process = Process(self.config, self.target, self.sockets)\n            process.start()\n            self.processes.append(process)\n\n    def terminate_all(self) -> None:\n        for process in self.processes:\n            process.terminate()\n\n    def join_all(self) -> None:\n        for process in self.processes:\n            process.join()\n\n    def restart_all(self) -> None:\n        for idx, process in enumerate(self.processes):\n            process.terminate()\n            process.join()\n            new_process = Process(self.config, self.target, self.sockets)\n            new_process.start()\n            self.processes[idx] = new_process\n\n    def run(self) -> None:\n        message = f\"Started parent process [{os.getpid()}]\"\n        color_message = \"Started parent process [{}]\".format(click.style(str(os.getpid()), fg=\"cyan\", bold=True))\n        logger.info(message, extra={\"color_message\": color_message})\n\n        self.init_processes()\n\n        while not self.should_exit.wait(0.5):\n            self.handle_signals()\n            self.keep_subprocess_alive()\n\n        self.terminate_all()\n        self.join_all()\n\n        message = f\"Stopping parent process [{os.getpid()}]\"\n        color_message = \"Stopping parent process [{}]\".format(click.style(str(os.getpid()), fg=\"cyan\", bold=True))\n        logger.info(message, extra={\"color_message\": color_message})\n\n    def keep_subprocess_alive(self) -> None:\n        if self.should_exit.is_set():\n            return  # parent process is exiting, no need to keep subprocess alive\n\n        for idx, process in enumerate(self.processes):\n            if process.is_alive(timeout=self.config.timeout_worker_healthcheck):\n                continue\n\n            process.kill()  # process is hung, kill it\n            process.join()\n\n            if self.should_exit.is_set():\n                return  # pragma: full coverage\n\n            logger.info(f\"Child process [{process.pid}] died\")\n            process = Process(self.config, self.target, self.sockets)\n            process.start()\n            self.processes[idx] = process\n\n    def handle_signals(self) -> None:\n        for sig in tuple(self.signal_queue):\n            self.signal_queue.remove(sig)\n            sig_name = SIGNALS[sig]\n            sig_handler = getattr(self, f\"handle_{sig_name.lower()}\", None)\n            if sig_handler is not None:\n                sig_handler()\n            else:  # pragma: no cover\n                logger.debug(f\"Received signal {sig_name}, but no handler is defined for it.\")\n\n    def handle_int(self) -> None:\n        logger.info(\"Received SIGINT, exiting.\")\n        self.should_exit.set()\n\n    def handle_term(self) -> None:\n        logger.info(\"Received SIGTERM, exiting.\")\n        self.should_exit.set()\n\n    def handle_break(self) -> None:  # pragma: py-not-win32\n        logger.info(\"Received SIGBREAK, exiting.\")\n        self.should_exit.set()\n\n    def handle_hup(self) -> None:  # pragma: py-win32\n        logger.info(\"Received SIGHUP, restarting processes.\")\n        self.restart_all()\n\n    def handle_ttin(self) -> None:  # pragma: py-win32\n        logger.info(\"Received SIGTTIN, increasing the number of processes.\")\n        self.processes_num += 1\n        process = Process(self.config, self.target, self.sockets)\n        process.start()\n        self.processes.append(process)\n\n    def handle_ttou(self) -> None:  # pragma: py-win32\n        logger.info(\"Received SIGTTOU, decreasing number of processes.\")\n        if self.processes_num <= 1:\n            logger.info(\"Already reached one process, cannot decrease the number of processes anymore.\")\n            return\n        self.processes_num -= 1\n        process = self.processes.pop()\n        process.terminate()\n        process.join()\n"
  },
  {
    "path": "uvicorn/supervisors/statreload.py",
    "content": "from __future__ import annotations\n\nimport logging\nfrom collections.abc import Callable, Iterator\nfrom pathlib import Path\nfrom socket import socket\n\nfrom uvicorn.config import Config\nfrom uvicorn.supervisors.basereload import BaseReload\n\nlogger = logging.getLogger(\"uvicorn.error\")\n\n\nclass StatReload(BaseReload):\n    def __init__(\n        self,\n        config: Config,\n        target: Callable[[list[socket] | None], None],\n        sockets: list[socket],\n    ) -> None:\n        super().__init__(config, target, sockets)\n        self.reloader_name = \"StatReload\"\n        self.mtimes: dict[Path, float] = {}\n\n        if config.reload_excludes or config.reload_includes:\n            logger.warning(\"--reload-include and --reload-exclude have no effect unless watchfiles is installed.\")\n\n    def should_restart(self) -> list[Path] | None:\n        self.pause()\n\n        for file in self.iter_py_files():\n            try:\n                mtime = file.stat().st_mtime\n            except OSError:  # pragma: nocover\n                continue\n\n            old_time = self.mtimes.get(file)\n            if old_time is None:\n                self.mtimes[file] = mtime\n                continue\n            elif mtime > old_time:\n                return [file]\n        return None\n\n    def restart(self) -> None:\n        self.mtimes = {}\n        return super().restart()\n\n    def iter_py_files(self) -> Iterator[Path]:\n        for reload_dir in self.config.reload_dirs:\n            for path in list(reload_dir.rglob(\"*.py\")):\n                yield path.resolve()\n"
  },
  {
    "path": "uvicorn/supervisors/watchfilesreload.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable\nfrom pathlib import Path\nfrom socket import socket\n\nfrom watchfiles import watch\n\nfrom uvicorn.config import Config\nfrom uvicorn.supervisors.basereload import BaseReload\n\n\nclass FileFilter:\n    def __init__(self, config: Config):\n        default_includes = [\"*.py\"]\n        self.includes = [default for default in default_includes if default not in config.reload_excludes]\n        self.includes.extend(config.reload_includes)\n        self.includes = list(set(self.includes))\n\n        default_excludes = [\".*\", \".py[cod]\", \".sw.*\", \"~*\"]\n        self.excludes = [default for default in default_excludes if default not in config.reload_includes]\n        self.exclude_dirs = []\n        for e in config.reload_excludes:\n            p = Path(e)\n            try:\n                is_dir = p.is_dir()\n            except OSError:  # pragma: no cover\n                # gets raised on Windows for values like \"*.py\"\n                is_dir = False\n\n            if is_dir:\n                self.exclude_dirs.append(p)\n            else:\n                self.excludes.append(e)  # pragma: full coverage\n        self.excludes = list(set(self.excludes))\n\n    def __call__(self, path: Path) -> bool:\n        for include_pattern in self.includes:\n            if path.match(include_pattern):\n                if str(path).endswith(include_pattern):\n                    return True  # pragma: full coverage\n\n                for exclude_dir in self.exclude_dirs:\n                    if exclude_dir in path.parents:\n                        return False\n\n                for exclude_pattern in self.excludes:\n                    if path.match(exclude_pattern):\n                        return False  # pragma: full coverage\n\n                return True\n        return False\n\n\nclass WatchFilesReload(BaseReload):\n    def __init__(\n        self,\n        config: Config,\n        target: Callable[[list[socket] | None], None],\n        sockets: list[socket],\n    ) -> None:\n        super().__init__(config, target, sockets)\n        self.reloader_name = \"WatchFiles\"\n        self.reload_dirs: list[Path] = []\n        for directory in config.reload_dirs:\n            self.reload_dirs.append(directory)\n\n        self.watch_filter = FileFilter(config)\n        self.watcher = watch(\n            *self.reload_dirs,\n            watch_filter=None,\n            stop_event=self.should_exit,\n            # using yield_on_timeout here mostly to make sure tests don't\n            # hang forever, won't affect the class's behavior\n            yield_on_timeout=True,\n            ignore_permission_denied=True,\n        )\n\n    def should_restart(self) -> list[Path] | None:\n        self.pause()\n\n        changes = next(self.watcher)\n        if changes:\n            unique_paths = {Path(c[1]) for c in changes}\n            return [p for p in unique_paths if self.watch_filter(p)]\n        return None\n"
  },
  {
    "path": "uvicorn/workers.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport signal\nimport sys\nimport warnings\nfrom typing import Any\n\nfrom gunicorn.arbiter import Arbiter\nfrom gunicorn.workers.base import Worker\n\nfrom uvicorn._compat import asyncio_run\nfrom uvicorn.config import Config\nfrom uvicorn.server import Server\n\nwarnings.warn(\n    \"The `uvicorn.workers` module is deprecated. Please use `uvicorn-worker` package instead.\\n\"\n    \"For more details, see https://github.com/Kludex/uvicorn-worker.\",\n    DeprecationWarning,\n)\n\n\nclass UvicornWorker(Worker):\n    \"\"\"\n    A worker class for Gunicorn that interfaces with an ASGI consumer callable,\n    rather than a WSGI callable.\n    \"\"\"\n\n    CONFIG_KWARGS: dict[str, Any] = {\"loop\": \"auto\", \"http\": \"auto\"}\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n        logger = logging.getLogger(\"uvicorn.error\")\n        logger.handlers = self.log.error_log.handlers\n        logger.setLevel(self.log.error_log.level)\n        logger.propagate = False\n\n        logger = logging.getLogger(\"uvicorn.access\")\n        logger.handlers = self.log.access_log.handlers\n        logger.setLevel(self.log.access_log.level)\n        logger.propagate = False\n\n        config_kwargs: dict = {\n            \"app\": None,\n            \"log_config\": None,\n            \"timeout_keep_alive\": self.cfg.keepalive,\n            \"timeout_notify\": self.timeout,\n            \"callback_notify\": self.callback_notify,\n            \"limit_max_requests\": self.max_requests,\n            \"forwarded_allow_ips\": self.cfg.forwarded_allow_ips,\n        }\n\n        if self.cfg.is_ssl:\n            ssl_kwargs = {\n                \"ssl_keyfile\": self.cfg.ssl_options.get(\"keyfile\"),\n                \"ssl_certfile\": self.cfg.ssl_options.get(\"certfile\"),\n                \"ssl_keyfile_password\": self.cfg.ssl_options.get(\"password\"),\n                \"ssl_version\": self.cfg.ssl_options.get(\"ssl_version\"),\n                \"ssl_cert_reqs\": self.cfg.ssl_options.get(\"cert_reqs\"),\n                \"ssl_ca_certs\": self.cfg.ssl_options.get(\"ca_certs\"),\n                \"ssl_ciphers\": self.cfg.ssl_options.get(\"ciphers\"),\n            }\n            config_kwargs.update(ssl_kwargs)\n\n        if self.cfg.settings[\"backlog\"].value:\n            config_kwargs[\"backlog\"] = self.cfg.settings[\"backlog\"].value\n\n        config_kwargs.update(self.CONFIG_KWARGS)\n\n        self.config = Config(**config_kwargs)\n\n    def init_signals(self) -> None:\n        # Reset signals so Gunicorn doesn't swallow subprocess return codes\n        # other signals are set up by Server.install_signal_handlers()\n        # See: https://github.com/Kludex/uvicorn/issues/894\n        for s in self.SIGNALS:\n            signal.signal(s, signal.SIG_DFL)\n\n        signal.signal(signal.SIGUSR1, self.handle_usr1)\n        # Don't let SIGUSR1 disturb active requests by interrupting system calls\n        signal.siginterrupt(signal.SIGUSR1, False)\n\n    def _install_sigquit_handler(self) -> None:\n        \"\"\"Install a SIGQUIT handler on workers.\n\n        - https://github.com/Kludex/uvicorn/issues/1116\n        - https://github.com/benoitc/gunicorn/issues/2604\n        \"\"\"\n\n        loop = asyncio.get_running_loop()\n        loop.add_signal_handler(signal.SIGQUIT, self.handle_exit, signal.SIGQUIT, None)\n\n    async def _serve(self) -> None:\n        self.config.app = self.wsgi\n        server = Server(config=self.config)\n        self._install_sigquit_handler()\n        await server.serve(sockets=self.sockets)\n        if not server.started:\n            sys.exit(Arbiter.WORKER_BOOT_ERROR)\n\n    def run(self) -> None:\n        return asyncio_run(self._serve(), loop_factory=self.config.get_loop_factory())\n\n    async def callback_notify(self) -> None:\n        self.notify()\n\n\nclass UvicornH11Worker(UvicornWorker):\n    CONFIG_KWARGS = {\"loop\": \"asyncio\", \"http\": \"h11\"}\n"
  }
]